{
  "markdown": "# endoflife.ai — MCP Server\n\n[![Node.js EOL status](https://img.shields.io/endpoint?url=https%3A%2F%2Fendoflife.ai%2Fbadge%2Fnodejs.json)](https://endoflife.ai/nodejs) [![EOL data: endoflife.ai](https://img.shields.io/badge/EOL%20data-endoflife.ai-16a34a)](https://endoflife.ai)\n\nExposes endoflife.ai's lifecycle intelligence to AI agents over the **Model Context\nProtocol** (MCP, Streamable HTTP transport). A dependency-free Cloudflare Worker\nthat wraps the public `api.endoflife.ai/v1` endpoints and the site's published\nfeeds — no data duplicated, every answer carries a source URL.\n\nCurrent version: **1.1.0** (`SERVER_INFO.version` in `src/index.js`, `server.json`,\n`package.json` — keep all three in step; the registry and Glama read `server.json`).\n\n## Tools (all read-only)\n\n| Tool | What it does | Backed by |\n|---|---|---|\n| `check_eol` | Is product X version Y end-of-life? | `GET /v1/status/:slug/:version` |\n| `get_risk_score` | EOL Risk Score (0–100) + factor breakdown | `GET /v1/score/:slug[/:version]` |\n| `scan_stack` | Score a whole stack at once | `POST /v1/batch` |\n| `list_products` | Search the 500+ tracked products → resolve slugs | `GET /v1/products` |\n| `get_product_lifecycle` | Full version history + dates for one product | `GET /v1/product/:slug` |\n| `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` |\n| `get_upcoming_eol` | Everything reaching EOL in the next N days (catalog-wide or a product list) | `endoflife.ai/scanner-db.json` |\n| `get_edge_device_status` | EOS Edge Device feed with BOD 26-02 statuses, filter by platform / status / model | `endoflife.ai/eos-edge-devices.json` |\n| `get_upgrade_path` | Supported targets, the site's recommendation, vendor-stated successor | `GET /v1/product/:slug`, `checker-db.json`, edge feed |\n| `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` |\n\nEvery tool advertises `annotations` (`readOnlyHint`, `idempotentHint`) and a\npermissive `outputSchema`; results are returned both as JSON text and as\n`structuredContent`. Lookup misses return \"did you mean\" slug suggestions\n(prefix / substring / edit-distance ≤ 2 against the live product list).\n\n**Resources** (`resources/list` / `resources/read`): `llms.txt`, the EOS Edge Device\nfeed, Exploited & Unpatchable, KEV by product, the edge change log.\n\n**Prompts** (`prompts/list` / `prompts/get`): `audit_stack`, `eol_calendar`,\n`edge_device_review`.\n\n**Endpoints:** `POST /` (JSON-RPC), `GET /` (info page), `GET /health` (liveness JSON),\n`GET /.well-known/mcp/server-card.json` (discovery card).\n\n## Usage telemetry\n\nWith the `USAGE` Analytics Engine binding (see `wrangler.toml`, dataset\n`endoflife_mcp_usage`) each method / tool call writes one data point: tool name,\nclient user agent, ok/error, keyed/anon, latency in ms. No request bodies. Query\nvia the Cloudflare Analytics Engine SQL API, e.g.\n\n```sql\nSELECT blob1 AS tool, count() AS calls, sum(_sample_interval) AS weighted\nFROM endoflife_mcp_usage WHERE timestamp > NOW() - INTERVAL '7' DAY\nGROUP BY tool ORDER BY calls DESC\n```\n\nThe binding is optional; the code no-ops without it.\n\n## Deploy\n\nProduction deploys run from GitHub Actions, never a local wrangler:\n\n```bash\ngh workflow run mcp-server-deploy.yml\n```\n\n(`.github/workflows/mcp-server-deploy.yml`, pinned wrangler; needs the\n`CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` repository secrets.) Local\n`wrangler dev` still works for development; the `API` service binding and the\n`USAGE` dataset are declared for both the default and `production` environments.\n\n### Custom domain (mcp.endoflife.ai)\n\nThe `route` in `wrangler.toml` needs `mcp.endoflife.ai` to resolve through Cloudflare:\nan **AAAA** record, name **mcp**, IPv6 **100::**, proxied. Already in place.\n\n### Test before deploying\n\nA Node harness exercises every method against the live API and feeds (the module\nruns unchanged in Node with `env.API = { fetch }`):\n\n```bash\nnode scratch/mcp_test.mjs   # see the session scratchpad; 22 RPC calls, expects 0 failures\n```\n\n## Run as a container (Red Hat UBI)\n\n`server.mjs` runs the same handler that serves mcp.endoflife.ai inside a plain Node.js process, and `Dockerfile` packages it on `registry.access.redhat.com/ubi9/nodejs-22-minimal` for cluster deploys (the form the OpenShift AI MCP catalog expects). No build step, no dependencies; the container talks to `https://api.endoflife.ai` over HTTPS and runs as the unprivileged UBI user (uid 1001).\n\n```\ndocker build -t endoflife-mcp mcp-server/\ndocker run --rm -p 8080:8080 endoflife-mcp\ncurl -s localhost:8080/health\n```\n\nEndpoints are the Worker's own: `POST /` (Streamable HTTP JSON-RPC), `GET /health` (readiness), `GET /.well-known/mcp/server-card.json`. Set `ENDOFLIFE_API_KEY` to forward a Pro key. Validated on RHEL 9 and 10 with Node 20 and 22 by `.github/workflows/rhel-validation.yml` (evidence in `docs/company/redhat-validation-evidence.md`).\n\n## Connect from an MCP client\n\n**Claude Desktop / Cursor / VS Code** (`mcpServers`):\n\n```json\n{\n  \"mcpServers\": {\n    \"endoflife\": { \"command\": \"npx\", \"args\": [\"mcp-remote\", \"https://mcp.endoflife.ai\"] }\n  }\n}\n```\n\nClients with native remote-MCP support can use the URL directly:\n\n```json\n{ \"mcpServers\": { \"endoflife\": { \"url\": \"https://mcp.endoflife.ai\" } } }\n```\n\n## Test without a client\n\n```bash\n# tools/list\ncurl -s https://mcp.endoflife.ai -X POST -H 'Content-Type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}' | jq\n\n# KEV exposure for a product\ncurl -s https://mcp.endoflife.ai -X POST -H 'Content-Type: application/json' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"get_kev_exposure\",\"arguments\":{\"product\":\"ivanti-connect-secure\"}}}' | jq\n\n# health + discovery card\ncurl -s https://mcp.endoflife.ai/health | jq\ncurl -s https://mcp.endoflife.ai/.well-known/mcp/server-card.json | jq\n```\n\n## Auth & tiers\n\nFree tier works with no key. Forward an `X-API-Key` header (your existing Pro keys)\nto unlock Pro limits — the Worker passes it straight through to `api.endoflife.ai`.\n\n## Notes\n\n- **Same-zone trap:** `api.endoflife.ai` is a Worker on this zone; a plain `fetch()`\n  to it goes to origin and fails. The `API` service binding is mandatory in prod.\n- **Static feeds** are fetched from the site origin with `cf.cacheTtl = 600` and\n  memoised in the isolate for 10 minutes.\n- **Per-client rate limits:** relies on the upstream API's limits. If MCP traffic\n  grows, add a KV rate-limiter keyed on `CF-Connecting-IP` before the upstream call.\n- **Discovery card** is also served from the main site at\n  `https://endoflife.ai/.well-known/mcp/server-card.json`.\n",
  "bytes": 6921,
  "sha": "f316e8d63618b75074456d44b44d96ba1497a9b3d357c899336afc49193d39ef",
  "repo_slug": "endoflife-ai/endoflife-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_endoflife_ai_endoflife_mcp_c91bb191/readme"
}