Skip to main content
Glama

CF Package Check

Is this WordPress plugin, npm package or PyPI project abandoned?

A pay-per-call answer computed live from the public registries, on Cloudflare Workers. Charged per request with x402, and exposed to agents as an MCP tool. Every answer lists the signals behind its score.

Signals, not a verdict. The score is computed from public registry metadata; it is not a security or code-quality review. Check the project before deciding.

Live: https://cf-package-check.cf-exclusion-check.workers.dev Network: Base mainnet (eip155:8453), USDC, via the Coinbase CDP facilitator. Payments are real. Settlement happens after the handler succeeds, so a request that cannot be answered is never charged.

This is booth #2. Its payment, discovery, MCP, rate-limiting and logging code is copied from booth #1 (cf-exclusion-check).


Endpoints

endpoint

price

notes

GET /v1/check?ecosystem=…&name=…

$0.02

status, score, months since update, last release, signals

GET /v1/report?ecosystem=…&name=…

$0.10

everything above + breakdown, raw registry facts, GitHub facts, version history, support/issue stats, recommendation

GET /v1/health

free

registry reachability, cache and payment configuration

GET /.well-known/x402 (also …/x402.json)

free

discovery document, discoverable: true

POST /mcp

$0.02 per package_check call

JSON-RPC; discovery and package_sources are free

ecosystem is wordpress, npm or pypi. name is the npm package (express, @types/node), the PyPI project (requests; normalised per PEP 503), or the WordPress.org plugin slug (contact-form-7, as in wordpress.org/plugins/<slug>/).

Only GET is served on the paid paths. The query is validated after the paywall: CDP's Bazaar probes the bare resource URL and requires a 402, so a bare GET /v1/check answers with the payment challenge. A paid request with a malformed query gets 400, and a response of 400 or above is never settled, so it costs nothing.

/v1/check response

{
  "ecosystem": "npm",
  "name": "request",
  "exists": true,
  "status": "deprecated",
  "score": 100,
  "months_since_update": 78,
  "last_release": { "version": "2.88.2", "date": "2020-02-11T16:35:36.122Z" },
  "signals": [
    "deprecated on npm: \"request has been deprecated, see https://github.com/request/request/issues/3142\"",
    "last update 78 months ago (2020-02-11, 2.88.2)",
    "50,778,952 downloads last month"
  ],
  "as_of": "2026-09-11T07:36:46.761Z",
  "disclaimer": "Signals, not a verdict. …"
}

status is one of active, stale, abandoned, deprecated, closed. score is abandonment risk, 0–100. as_of is when the registry facts were read (up to 24 h old on a cache hit). A package that does not exist is an answer, not an error: exists: false, with status and score null.

/v1/report adds

score_breakdown (every rule that fired and its points), recommendation, registry (the raw facts per ecosystem), github (archived, last push, open issues, stars — or why it was not checked), versions (count, first release, releases in the last 12 months, the ten newest), support (WordPress support threads and resolution rate; GitHub open issues + PRs), partial (optional enrichment that was skipped), sources (the upstream URLs used), cached, checked_at.


Related MCP server: address-intel-api

Scoring

Deterministic: a pure function of the registry facts and the current date (src/score.ts, asserted rule by rule in test/score.test.ts).

rule

points

months since last update: 0–5

0

6–11

25

12–23

50

24–35

70

36+ (or no dated release at all)

90

GitHub repository archived

+20

WordPress "tested up to" ≥ 3 major releases behind current WordPress

+10

npm: < 100 downloads last month and first published > 2 years ago

+10

cap

100

registry flag

score

status

closed on WordPress.org, or unpublished from npm

100

closed

deprecated on npm, or every release yanked on PyPI

100

deprecated

Without a flag: score < 25 → active, < 50 → stale, otherwise abandoned. A score of 100 reached by stacking rules is still abandoned; deprecated and closed are reserved for the registry itself saying so.

Months = floor(days since last update / 30.4375), so band edges are exact whole months.

"Last update" is chosen per ecosystem, and the report states which:

ecosystem

last update means

why not the obvious field

npm

newest publish of any version

express's latest tag (5.2.1) dates from 2025-12-01, but 4.22.2 shipped 2026-05-11 on the maintained 4.x line. time.modified moves on metadata-only changes and is never scored.

PyPI

newest file upload to any release

adding wheels to an old release is maintenance

WordPress

last_updated

the only date the API has; it also moves on readme-only commits

WordPress majors are X.Y (6.8, 6.9, 7.0 …), so "3 majors behind" is (10·X + Y) of current minus that of tested-up-to.

Worked examples (2026-09-11)

package

result

why

wordpress contact-form-7

active, 0

updated 2026-08-17, tested up to 7.1 = current

wordpress wp-cycle

abandoned, 100

168 months (90) + tested up to 3.4.2, 37 majors behind 7.1 (+10)

wordpress exec-php

closed, 100

closed 2018-12-14, Security Issue

npm express

active, 0

last publish 4 months ago (4.22.2)

npm request

deprecated, 100

deprecated by its maintainers

npm flux

abandoned, 100

41 months (90) + repo archived as facebookarchive/flux (+20), capped

pypi requests

active, 0

last upload 3 months ago

pypi pycrypto

abandoned, 90

last upload 2014-06-20


Paying

An unpaid call returns 402 with the machine-readable challenge in the payment-required header (base64 JSON, x402 v2):

{"x402Version":2,"accepts":[{"scheme":"exact","network":"eip155:8453",
  "amount":"20000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo":"0xCa28eb92657F8a81aFb5493b3a04A740B204d816","maxTimeoutSeconds":60,
  "extra":{"name":"USD Coin","version":"2","paymentFlow":"authorization"}}],
 "extensions":{"bazaar":{…}}}

amount is atomic USDC (6 decimals): 20000 = $0.02, 100000 = $0.10.

curl -i "https://cf-package-check.cf-exclusion-check.workers.dev/v1/check?ecosystem=npm&name=express"   # 402

node scripts/x402-client.mjs                          # pays $0.02 for /v1/check npm express
node scripts/x402-client.mjs /v1/report pypi requests # pays $0.10

The client refuses to pay unless /v1/health answers 200 and the payer holds at least $0.05, then waits for the receipt and checks the USDC Transfer to PAY_TO on chain. Gas is paid by the facilitator under EIP-3009, so a payer needs USDC only.

MCP

{
  "mcpServers": {
    "cf-package-check": {
      "type": "http",
      "url": "https://cf-package-check.cf-exclusion-check.workers.dev/mcp"
    }
  }
}
curl -X POST https://cf-package-check.cf-exclusion-check.workers.dev/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

curl -X POST https://cf-package-check.cf-exclusion-check.workers.dev/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"package_check","arguments":{"ecosystem":"npm","name":"express"}}}'
# -> 402 until paid; an x402-capable client pays and retries

tool

price

returns

package_check

$0.02 per call

the /v1/check body as structuredContent; the text leads with the status

package_sources

free

upstream endpoints, the scoring table, the cache policy

initialize, ping and tools/list are free, so an agent can learn the tool and its price before paying. Bad arguments are answered as a free tool error before the paywall. A registry outage is HTTP 503, not a 200 carrying an error, so the payment is never settled for it.


How it works

Stateless. Each answer is read live from:

ecosystem

upstream

WordPress

api.wordpress.org/plugins/info/1.2/ (heavy fields switched off: 42 KB → 17 KB) and core/version-check/1.7/

npm

registry.npmjs.org/{name} (streamed, see below), /{name}/latest, api.npmjs.org/downloads/point/last-month/{name}

PyPI

pypi.org/pypi/{name}/json

GitHub (optional)

api.github.com/repos/{owner}/{repo} when the registry links a repo; unauthenticated

npm packuments are never parsed whole. typescript's is 15.6 MB and a full JSON.parse costs ~31 ms of CPU against the free plan's 10 ms. The packument is streamed, searched byte-wise for "time":{, and only that object is decoded; the stream is cancelled once it closes. docs/SOURCES.md has the measurements.

WordPress answers 404 for both "closed" and "never existed"; only the body tells them apart. Closed plugins are reported as closed with the date and reason, not as a typo.

Cache and free-tier discipline

  • Registry facts are cached in KV for 24 h — one combined record per package, because the free plan allows 1,000 KV writes a day. When optional enrichment was skipped (GitHub rate-limited, download count or current WordPress version unavailable) the TTL is 1 h so it is retried soon.

  • Registry failures are never cached and are answered 503, which the authorization payment flow never settles.

  • No request scans anything unbounded: npm reads are capped by MAX_PACKUMENT_BYTES (32 MiB), PyPI by 16 MB, WordPress by 2 MB.

  • /v1/health probes are memoised for 60 s per isolate, because health is never rate limited.

  • D1 holds only the request audit log.

Limits

rate limit, free

10 / min per (salted-hash) IP — approximate: Cloudflare's rate-limit binding is eventually consistent per location, and a live burst passed 12 requests before the first 429

rate limit, paid (request carries a payment header)

60 / min, same caveat

never rate limited

/v1/health, /.well-known/x402

freshness

facts up to 24 h old; as_of says exactly

GitHub

unauthenticated, 60 requests/hour per egress IP shared with other Workers; when limited, the archived rule is skipped and the answer says so

ecosystems

WordPress.org plugins, npm, PyPI only

CPU

the largest packuments (typescript, aws-sdk, next) answered 200 on an edge preview; if production ever returns 1102 for them, lower MAX_PACKUMENT_BYTES and they get a clean, uncharged 503

D1

free-tier D1 quotas are account-wide, shared with booth #1. Audit-log writes fail soft (logged to the console) when the account's budget is spent — which happened on launch day: see below

Mainnet verification (2026-09-11)

check

result

unpaid /v1/check, /v1/report, MCP package_check

402, eip155:8453, 20000 / 100000 atomic USDC to 0xCa28…d816, paymentFlow: authorization, bazaar extension present (scripts/live-test.mjs, 26/26)

paid GET /v1/check?ecosystem=npm&name=express

200 — active, score 0

settlement

0x8b9bc81b018f1cb8bc546ef8143726a30d4d73a2aa8895259aafa8c3c91606cf, Base block 51162136, status success

on-chain transfer

USDC Transfer of 20000 atomic ($0.02) from payer 0xE8eA…1117 to PAY_TO; balances payer 1.00 → 0.98, PAY_TO 0.00 → 0.02

Bazaar, first attempt

not listed: bare GET /v1/check answered 400 because input was validated before the paywall, and CDP /validate rejected it ("Endpoint returned HTTP 400 instead of 402")

fix + indexing payment

input now validated after the paywall (version 80c7a879); CDP /validate accepts bare /v1/check and /v1/report; paid /v1/check settled in 0xe1874c320dbb74ad5cc1818cedc9b122deac9485e880a29f1a150e0c6e8c5e5b, block 51180211, 20000 atomic USDC to PAY_TO

Bazaar

listed about 10 minutes after that settlement: the merchant lookup shows …/v1/check (lastUpdated 2026-09-11T18:02:47Z). /v1/report is not listed because it has never been paid for. Keepalive due before 2026-10-11 (see below).

audit log

the paid request's row was not written: the account's D1 free-tier quota was already spent by booth #1 (13.4M rows read in 24h), so most inserts that day failed with D1_ERROR: Your account has exceeded D1's free tier daily row read limit — only 6 rows landed, intermittently, in the first 15 minutes of traffic. The request was served; the settlement is on chain; Workers Logs recorded the invocation.

Staying listed in the Bazaar (30-day keepalive)

CDP's Bazaar indexes a route after a payment for it settles through the CDP facilitator, and removes it again:

  • after 30 days without a settlement. Every listed route needs at least one settled paid call inside every 30-day window. Run a keepalive at least every three weeks: node scripts/x402-client.mjs ($0.02, /v1/check), and node scripts/x402-client.mjs /v1/report npm express ($0.10) if the report route should stay listed too. A route that has never been paid for is not listed at all.

  • when its bare URL stops answering 402 Payment Required. Keep bare GET /v1/check and GET /v1/report returning the challenge; that is why input is validated after the paywall.

Ranking is recomputed every six hours. To check, with no API key and no payment:

# would CDP accept the route for indexing?
curl -s -X POST https://api.cdp.coinbase.com/platform/v2/x402/validate \
  -H 'Content-Type: application/json' \
  -d '{"resource":"https://cf-package-check.cf-exclusion-check.workers.dev/v1/check","method":"GET"}'

# everything the Bazaar lists for this payout address (shared with booth #1)
curl -s "https://api.cdp.coinbase.com/platform/v2/x402/discovery/merchant?payTo=0xCa28eb92657F8a81aFb5493b3a04A740B204d816"

Development

npm install
npm test                         # 129 tests, on recorded registry fixtures
npm run typecheck
npm run fixtures:record          # re-record test/fixtures from the live registries

npx wrangler dev                 # local; set PAY_TO="" in .dev.vars to disable payment
BASE_URL=http://127.0.0.1:8787 node scripts/live-test.mjs --open   # live registries
node scripts/live-test.mjs       # paywall checks against the deployed Worker (spends nothing)

Deploying:

npx wrangler d1 execute cf_package_check --remote --file migrations/0001_init.sql
npx wrangler deploy
openssl rand -hex 32 | npx wrangler secret put IP_HASH_SALT
npx wrangler secret put CDP_API_KEY_ID        # same CDP key as booth #1
npx wrangler secret put CDP_API_KEY_SECRET

Without the CDP secrets the paid paths answer 503 "payment facilitator unavailable" (no payment taken) — the public facilitator does not support Base mainnet.

Privacy

  • Raw client IPs are never stored: they are HMAC'd with IP_HASH_SALT, and with no salt no hash is written at all.

  • The queried package is logged only as a salted hash, matching booth #1's schema.

  • No analytics.

Future work

  • Chrome Web Store — it has no public API for extension metadata, so it is out of scope until one exists or a sanctioned data source appears.

  • Other ecosystems (crates.io, RubyGems, Packagist, Maven Central) would slot in as further adapters behind the same scoring.

Licence

MIT. Registry data belongs to its publishers (WordPress.org, npm, the Python Package Index, GitHub). This project is not affiliated with or endorsed by the WordPress Foundation, npm, Inc., GitHub, Inc. or the Python Software Foundation.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    Agent payments ecosystem intelligence. Scans GitHub, Hacker News, and npm for activity across AP2, ACP, x402, MPP, and UCP protocols. Returns scored and classified opportunities. Free protocol info and comparison, paid scan via x402 USDC
    3
    201
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides counterparty risk checks for any EVM address, returning malicious-history flags, tiered risk scoring, and plain-language summaries via x402 payment.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides software supply-chain intelligence for AI agents, enabling them to query package metadata, versions, downloads, dependencies, and health signals for npm, PyPI, and crates.io packages without API keys.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Provides coding agents with 19 pay-per-call developer utilities, npm supply-chain security checks, and Base blockchain lookups, paid via USDC on Base using x402. No API key needed—payment acts as authentication.
    61
    -