pkg-oracle
An MCP server that verifies npm and PyPI packages before installation, checking for existence, known vulnerabilities, OpenSSF scorecard, and typosquatting, returning an ALLOW/WARN/BLOCK verdict.
README
pkg-oracle — Dependency Trust Oracle
A pay-per-call MCP (Model Context Protocol) server that verifies an npm or PyPI package before an AI coding agent writes it into a manifest. Monetized per call with the real x402 protocol (signed EIP-3009 authorizations, settled through Coinbase's hosted facilitator) in USDC on Base — no API keys, no signup, no dashboard. An agent calls the tool, pays a fraction of a cent, gets a verdict.
Why this exists
AI coding agents hallucinate package names and skip the verification a human developer would normally do — they check one thing ("does this name resolve?") and if it resolves, they install it. That gap is now an active attack surface known as slopsquatting: attackers register the package names LLMs are statistically likely to hallucinate, then wait.
verify_package closes that gap with a single tool call that:
- Confirms the package actually exists on its registry (npm or PyPI).
- Cross-references OSV.dev for known CVEs affecting the resolved version.
- Pulls the package's OpenSSF Scorecard via deps.dev (branch protection, code review practices, maintenance signal, ...).
- Runs a Levenshtein-distance typosquat check against a curated list of high-value popular package names, combined with the package's publish age — a near-miss spelling that's also brand new is the exact shape of a typosquat/slopsquat attack.
It returns one synthetic verdict an agent can act on without reasoning about four different data sources itself: ALLOW, WARN, or BLOCK.
Architecture
Agent (Claude, Cursor, custom SDK / x402-aware MCP client)
│ MCP tools/call verify_package (_meta["x402/payment"] once paid)
▼
StreamableHTTPServerTransport (fresh per request, stateless mode)
│
▼
McpServer → verify_package
│
├─ under free tier? ──▶ raw handler (no charge)
│
└─ else ──▶ x402 payment wrapper (@x402/mcp)
│
├──▶ CDP facilitator: verify signed payment, settle on-chain
└──▶ raw handler
│
├──▶ registry.ts (registry.npmjs.org | pypi.org)
├──▶ osv.ts (api.osv.dev)
├──▶ depsdev.ts (api.deps.dev — OpenSSF Scorecard)
└──▶ typosquat.ts (fast-levenshtein vs. curated popular list)
│
▼
oracle.ts aggregates → { verdict, findings[] }
Payment gating happens per MCP tool, not per HTTP route — initialize
and tools/list are never charged, only an actual verify_package call
past the free tier is.
Quickstart
npm install
cp .env.example .env # RECIPIENT_WALLET + CDP_API_KEY_ID/SECRET are required, even in dev
npm run build
npm start
Or for local iteration with hot reload:
npm run dev
Unlike most values in .env.example, RECIPIENT_WALLET and the two CDP
credentials have no safe placeholder — the resource server authenticates
against CDP's real hosted facilitator at boot, regardless of environment.
There's no way to run this server against fake/mock payment infrastructure.
The server listens on PORT (default 3000) and exposes:
| Endpoint | Method | Purpose |
|---|---|---|
/mcp |
POST |
MCP tool calls — verify_package is payment-gated past the free tier |
/mcp |
GET, DELETE |
Streamable HTTP protocol completeness (no-ops) |
/health |
GET |
Liveness check, always free |
Environment variables
See .env.example for the full list with defaults.
| Variable | Default | Meaning |
|---|---|---|
RECIPIENT_WALLET |
(dev-only burn address) | Base L2 wallet that receives USDC payments — required |
CDP_API_KEY_ID |
(none — required) | Coinbase Developer Platform API key ID |
CDP_API_KEY_SECRET |
(none — required) | Coinbase Developer Platform API key secret |
PRICE_ATOMIC_USDC |
3000 (= $0.003) |
Price per call once the free tier is spent |
FREE_TIER_LIMIT |
5 |
Free calls per wallet/IP before payment is required |
UPSTREAM_TIMEOUT_MS |
8000 |
Timeout for registry/OSV/deps.dev calls |
NEW_PACKAGE_THRESHOLD_DAYS |
30 |
Age under which a package is "new" |
TYPOSQUAT_MAX_DISTANCE |
2 |
Max Levenshtein distance still flagged |
The verify_package tool
Input schema:
{
"ecosystem": "npm | pypi",
"name": "string (required)",
"version": "string (optional — exact version to check)"
}
Example call and response:
// request
{ "ecosystem": "npm", "name": "expres" }
// response (verdict text + machine-readable JSON in content[])
{
"verdict": "WARN",
"findings": [
{
"code": "TYPOSQUAT_NAME_SIMILARITY",
"message": "\"expres\" is close (distance 1) to the popular package \"express\". ..."
}
],
"registry": { "exists": true, "ageDays": 4800, "...": "..." },
"osv": { "vulnerabilities": [], "highestSeverity": "UNKNOWN" },
"scorecard": { "overallScore": 1.5, "sourceRepo": "github.com/..." },
"typosquat": { "suspected": true, "distance": 1, "redFlag": false }
}
When no version is given, the oracle resolves the registry's latest
published version internally before querying OSV/deps.dev — querying OSV
with no version at all returns every vulnerability ever disclosed for the
package, patched or not, which would make a long-lived, well-maintained
package like lodash permanently read as BLOCK. A CHECKED_LATEST_VERSION
finding tells you which version was actually evaluated.
Verdict logic (first match wins)
- BLOCK — the name doesn't exist on the registry at all (the strongest possible hallucination/slopsquat signal).
- BLOCK — a pinned version was requested but doesn't exist.
- BLOCK — near-miss of a popular package name and published
within
NEW_PACKAGE_THRESHOLD_DAYS. - BLOCK — a known CRITICAL or HIGH severity vulnerability applies.
- WARN — a MODERATE/LOW/UNKNOWN vulnerability, a new package without a typosquat match, an older near-miss name, or a low OpenSSF Scorecard.
- ALLOW — nothing above fired.
Monetization: x402 on Base, via the CDP facilitator
verify_package is wrapped with @x402/mcp's createPaymentWrapper in
src/mcpServer.ts — the real x402 "exact" scheme, not
a homegrown variant:
- The caller is identified by
Authorization: Bearer <wallet>(or anX-Wallet-Addressheader), falling back to the client IP Fly's edge proxy reports if neither is present. This identity is self-declared, not verified — nothing stops a client from rotating the header to claim a fresh free allowance.FREE_TIER_LIMITdefaults to5specifically because of this: the free tier is a landing ramp, not an authenticated quota, sized to keep casual abuse cheap to give away rather than to be unbeatable. - The first
FREE_TIER_LIMITcalls per identity run the raw handler directly, no payment involved — a tool that requires payment on the very first call never gets tried by an agent, and never gets adopted. - Past the free tier, a call without a payment payload gets a payment
error carrying a standard x402
PaymentRequireddescriptor (scheme: "exact",network: "eip155:8453",payTo: RECIPIENT_WALLET,amount: "3000"). Any x402-aware MCP client —x402MCPClientfrom@x402/mcp, or an agent SDK with built-in x402 support — signs an EIP-3009transferWithAuthorization(no gas, no waiting for confirmation) and retries with the payload in_meta["x402/payment"]. The server verifies the signature and settles the on-chain transfer through CDP's facilitator, then runs the tool.
Funds are never custodied by Coinbase. The facilitator only verifies
the client's signature and submits the resulting transfer on-chain
(payTo is RECIPIENT_WALLET, resolved at server startup from your own
env var) — CDP never holds the money mid-flight.
Settling through CDP's hosted facilitator (as opposed to a generic one)
is also what makes verify_package auto-discoverable in the
x402 Bazaar: the
bazaarResourceServerExtension registered on the resource server plus the
declareDiscoveryExtension({ toolName: "verify_package", ... }) call in
mcpServer.ts get indexed automatically the first time a real payment
settles — there's no separate registration step.
Client configuration
pkg-oracle speaks MCP over Streamable HTTP, so any MCP-compatible client
pointed at http://<host>:<port>/mcp works for the free tier. Paying past
the free tier requires an x402-aware MCP client. Below are the common
integrations.
Claude Desktop / Cursor (free tier only)
{
"mcpServers": {
"pkg-oracle": {
"type": "http",
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer 0xYourAgentWalletAddress"
}
}
}
}
Neither ships a built-in x402 signer today, so once the free tier is spent these clients will surface the payment-required error as a tool failure rather than paying automatically.
Custom agent with x402 support
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { wrapMCPClientWithPaymentFromConfig } from "@x402/mcp";
import { ExactEvmScheme } from "@x402/evm/exact/client";
const mcpClient = new Client({ name: "my-agent", version: "1.0.0" });
await mcpClient.connect(
new StreamableHTTPClientTransport(new URL("http://localhost:3000/mcp")),
);
// account: any viem-compatible signer holding USDC on Base
const x402Client = wrapMCPClientWithPaymentFromConfig(mcpClient, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});
const result = await x402Client.callTool("verify_package", {
ecosystem: "npm",
name: "expres",
});
if (result.paymentMade) {
console.log("Paid:", result.paymentResponse?.transaction);
}
autoPayment: true (the default) signs and retries automatically once a
payment-required error is received — no manual 402 handling needed.
Docker
docker build -t pkg-oracle .
docker run -p 3000:3000 \
-e RECIPIENT_WALLET=0xYourWalletAddress \
-e CDP_API_KEY_ID=your-cdp-key-id \
-e CDP_API_KEY_SECRET=your-cdp-key-secret \
-e FREE_TIER_LIMIT=5 \
pkg-oracle
The image is a multistage, non-root, production-only build (dev dependencies and TypeScript source are stripped from the final layer).
Despliegue en producción (Fly.io)
fly.toml ya está configurado para desplegar directamente desde el
Dockerfile — HTTPS automático, health check contra /health. Pasos:
# 1. Instala flyctl y autentícate (una sola vez)
curl -L https://fly.io/install.sh | sh
fly auth login
# 2. Desde la raíz del proyecto: crea la app (usa el fly.toml existente)
fly launch --no-deploy # detecta fly.toml, no lo sobreescribas si pregunta
# 3. Configura los secretos (nunca los pongas en fly.toml ni los commitees)
fly secrets set RECIPIENT_WALLET=0xTuWalletReal
fly secrets set CDP_API_KEY_ID=tu-cdp-key-id
fly secrets set CDP_API_KEY_SECRET=tu-cdp-key-secret
# 4. Despliega
fly deploy
# 5. Verifica
curl https://tu-app.fly.dev/health
Tu servidor queda con TLS ya resuelto en el dominio *.fly.dev que Fly
asigne (o el dominio custom que configures con fly certs add) — sin eso,
x402 no tiene sentido: el resource del reto de pago necesita ser una URL
real y segura para que un cliente x402 confíe en ella.
Antes de escalar a más de una máquina: el contador freemium vive en
memoria (LRU) por proceso. Con min_machines_running = 1 (el valor por
defecto en fly.toml) esto no es un problema. Si escalas horizontalmente,
un mismo wallet obtendría FREE_TIER_LIMIT llamadas gratis por
instancia en vez de en total — en ese punto, mueve el contador a Redis
(Fly ofrece Upstash Redis como addon) antes de subir min_machines_running.
Known limitations / honest scope notes
- Popular-package list is curated, not a live top-1000 feed. It's a
hand-picked shortlist of the highest-value typosquat targets in each
ecosystem (
src/services/popularPackages.ts), not a synced download-rank API. Good enough to catchexpres→express; won't catch a typo of a mid-tier package outside the list. Syncing against npm's/PyPI's real download-rank data on a cron is the natural next step. - The free-tier counter is in-memory (LRU), not persisted — it resets
on restart and doesn't share state across multiple server instances.
Fine at
min_machines_running: 1(the default); back it with Redis before scaling horizontally (see Despliegue en producción above). - Payment replay protection is delegated to the protocol itself — the
EIP-3009
noncein each signed authorization is enforced on-chain by USDC's contract, and CDP's facilitator rejects already-settled or expired authorizations. Nothing custom to maintain here. - Claude Desktop and Cursor have no built-in x402 signer — they work fine for the free tier, but a payment-required response surfaces as a tool failure rather than being paid automatically. Use an x402-aware MCP client (see Client configuration) to actually pay past the free tier.
- CVSS-vector severity estimation is a conservative heuristic, used
only when OSV doesn't supply an explicit
database_specific.severitystring (see the doc comment insrc/services/osv.ts).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。