Back to Browse

Horizon Pulse MCP Server

Developer ToolsModerate7.8MCP RegistryLocalRemote
Free

Server data from the Official MCP Registry

23 agent tools via x402: crypto data, tx checks, web fetch. MCP pays Base USDC; HTTP adds 3 rails.

About

23 agent tools via x402: crypto data, tx checks, web fetch. MCP pays Base USDC; HTTP adds 3 rails.

Remote endpoints: streamable-http: https://horizonpulse.dev/mcp

Security Report

7.8
Moderate7.8Low Risk

Horizon Pulse is a pay-per-call x402 API server (Next.js) with a local stdio MCP wrapper; the visible code shows SSRF guards on fetch/http/extract routes, pinned payTo addresses, and secrets sourced from environment variables rather than hardcoded values. Findings are limited to informational and low-severity notes: the server intentionally makes arbitrary outbound HTTP requests to user-supplied URLs (expected for a fetch/proxy product), the local MCP server holds a buyer wallet key that can spend funds within caps, and the provided source was truncated so the full dependency and route implementations could not be verified.

4 files analyzed · 4 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

File System Read

Reads files on your machine. Normal for tools that analyze or process local data.

process_spawn

Check that this permission is expected for this type of plugin.

How to Install & Connect

Available as Local & Remote

This plugin can run on your machine or connect to a hosted endpoint. during install.

Documentation

View on GitHub

From the project's GitHub README.

Horizon Pulse

Pay-per-call crypto market pulse, signals, funding, yield rankings, portfolio risk, gas, plus public URL fetch → clean text, universal http proxy, and extract → structured fields for AI agents, monetized with the x402 protocol on Base mainnet and Solana mainnet (USDC, exact, same price on both).

This repo is a Next.js App Router service ready to deploy (e.g. Vercel) and push to:

https://github.com/horizon-pulse/horizon-pulse.git

Public host

RoleURL
Canonicalhttps://horizonpulse.dev
Backup (Vercel)https://horizon-pulse-seven.vercel.app

Prefer horizonpulse.dev in agent docs, OpenAPI, and clients. The *.vercel.app URL remains a working fallback.

What it is

  • Agents hit live paid HTTP endpoints (crypto routes frozen; /api/fetch + /api/http + /api/extract + /api/x402-check + /api/bazaar-check + /api/market/search + /api/market/quote + /api/screenshot + /api/search + /api/pdf non-crypto LIVE — see below).
  • Unpaid requests receive HTTP 402 with x402 v2 requirements: canonical wire is the PAYMENT-REQUIRED header, with one accepts entry per network: Base (CAIP-2 eip155:8453, Base USDC, the treasury below) and Solana (CAIP-2 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp, USDC mint, the Solana payTo below). See docs/solana-rail.md.
  • Retry with an x402 v2 PAYMENT-SIGNATURE header. The facilitator (Coinbase CDP on Base, PayAI on Solana) verifies + settles, then the route returns live data.
  • No stubbed prices or fake APYs: Coinbase Exchange (CoinGecko fallback) for spot; CoinGecko OHLC (Coinbase candles fallback) for signals; OKX for perpetual funding (Binance/Bybit are often geo-blocked on Vercel); DefiLlama for yield pools; public RPC balanceOf for portfolio (real balances only); eth_feeHistory / eth_gasPrice for gas (real fees only); /api/fetch returns best-effort cleaned text from a requested public URL (SSRF-safe, size/time capped); /api/http is a raw universal proxy (filtered headers, text|base64 body, SSRF-safe) priced $0.01 for volume; /api/extract returns best-effort structured page fields from a URL or HTML (SSRF-safe, size/time capped) priced $0.015.
  • Never invent metrics; never advertise routes that 404; never cite a cached balance — /status reads live USDC balanceOf on payTo. Honest: current payTo is interim Coinbase-custodial (not Safe/multisig).

Catalog status (post first settlement)

FactValue
Live paid routesSee the table below — the frozen crypto routes + /api/fetch + /api/http + /api/extract + /api/x402-check + /api/bazaar-check + /api/market/search + /api/market/quote + /api/screenshot + /api/search + /api/pdf
First settle/api/pulse $0.005 — tx 0xedbd1a51…
Crypto policyFrozen — six crypto routes: no price changes
FetchNon-crypto LIVE: clean-text ($0.02)
HttpNon-crypto LIVE: universal proxy ($0.01 volume price)
ExtractNon-crypto LIVE: structured HTML ($0.015)

Coming-soon / placeholder / 404 routes are not listed on /, /status, or this README.

Agent how-to (x402 v2)

  1. GET a paid route with no payment → HTTP 402.
  2. Read PAYMENT-REQUIRED (v2) and pick one accepts entry. Confirm network (eip155:8453 for Base or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp for Solana), USDC on that network, exact atomic amount, and that network's payTo.
  3. Sign and retry the same GET with PAYMENT-SIGNATURE (v2 primary; do not rely on legacy X-PAYMENT as the settle path).
  4. On success: the facilitator settles on the network you paid on; response body is the live JSON for that route.

Optional: OPTIONS on a paid route returns discovery + the same PAYMENT-REQUIRED challenge without charging.

Agent integration

  • Skill file: /skill.md, plain markdown an agent can follow end to end (generated from public/openapi.json by lib/agent-skill.ts).
  • Guide: /agents: pay flow, skill and MCP setup.
  • Local MCP server: mcp/, stdio, every live route as a tool, pays the x402 challenge from a buyer wallet you configure, with per-call and per-session caps (quote-only without a key). Not published to npm; install from this repo.
  • Hosted MCP: POST https://horizonpulse.dev/mcp (streamable HTTP) for MCP clients that can pay x402 themselves. Payments over MCP are USDC on Base only; the paid HTTP routes accept Base, Solana, Polygon and XRPL.

Treasury (payTo)

FieldValue
payTo0x5b32c973596078a967562ca652761404f19be0e9
Basenamehorizonpulsebase.base.eth (resolves to the payTo above; a label only, the 402 carries the hex address)
CustodyInterim Coinbase-custodial Base address (owner-controlled). Not a Safe or multisig.
Upgrade pathMove later to a non-custodial Safe / multisig when ready — same catalog and prices.
USDC (Base)0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Networkbase (CAIP-2 eip155:8453)
Facilitatorhttps://api.cdp.coinbase.com/platform/v2/x402
Solana payToBjY98A6dS3GGLZdz2zHy8wK7XAwnQgNhCc66mfmBTRPz (operator-controlled; pinned in lib/solana-config.ts; payments credit its USDC token account)
USDC (Solana)EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Solana networkCAIP-2 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (exact only)
Solana facilitatorPayAI https://facilitator.payai.network

Do not use the retired address 0xe16A1b12404cB2EbC6e783beCA6E2A9253c3dC7E.

PAY_TO may be overridden via env, but defaults to the address above. Agents should treat current payTo as the live settlement destination; custody shape may change later without changing route prices.

Endpoints

Paid (frozen crypto routes + fetch + http + extract + x402-check + bazaar-check + market/search + market/quote + screenshot + search + pdf):

RoutePriceAuth
GET /api/pulse$0.002 USDC (2000 atomic)x402 v2
GET /api/signals$0.005 USDC (5000 atomic)x402 v2
GET /api/yield$0.02 USDC (20000 atomic)x402 v2
GET /api/portfolio?address=0x…$0.04 USDC (40000 atomic)x402 v2
GET /api/gas$0.002 USDC (2000 atomic)x402 v2
GET /api/funding$0.003 USDC (3000 atomic)x402 v2
GET /api/fetch?url=https://…$0.02 USDC (20000 atomic)x402 v2
GET / POST /api/http$0.01 USDC (10000 atomic)x402 v2
GET / POST /api/extract$0.015 USDC (15000 atomic)x402 v2
GET /api/x402-check?url=https://…$0.01 USDC (10000 atomic)x402 v2
GET /api/bazaar-check?url=…$0.01 USDC (10000 atomic)x402 v2
GET /api/market/search?q=…$0.005 USDC (5000 atomic), untested pricex402 v2
GET /api/market/quote?url=https://…$0.005 USDC (5000 atomic), untested pricex402 v2
GET /api/screenshot?url=https://…$0.02 USDC (20000 atomic)x402 v2
GET /api/search?q=…&n=3$0.03 USDC (30000 atomic)x402 v2
GET /api/pdf?url=https://…$0.02 USDC (20000 atomic)x402 v2

Free (not paid APIs):

RouteAuth
GET /statuspublic HTML — live USDC balanceOf on payTo (honest RPC error if fetch fails)
GET /landing — live paid catalog + agent how-to

GET /api/pulse

Real spot prices for BTC / ETH / SOL (Coinbase Exchange, CoinGecko fallback) with per-asset momentum, plus overall momentum / sentiment / direction (up/down/flat, a neutral description of the average 24h move; renamed from signal, see CHANGELOG.md).

  • runtime = 'nodejs'
  • Discovery: Bazaar extension + outputSchema.input.discoverable: true in payment requirements

GET /api/signals

RSI(14), MACD(12,26,9), Bollinger bands from CoinGecko OHLC closes, plus OKX funding rates. Includes a methodology field.

  • runtime = 'nodejs'
  • Discoverable for agents

GET /api/yield

Ranked DeFi yield pools from the public DefiLlama yields API (https://yields.llama.fi/pools).

  • Hard filter: TVL ≥ $10M; exclude non-finite APY and DefiLlama outlier pools
  • Preference: keep stablecoin and/or single-asset (exposure === "single") pools
  • Transparent ranking: preferenceTier DESC (2 = stablecoin+single, 1 = either), then APY DESC, then TVL DESC
  • Every response includes a methodology object (also on error bodies)
  • Price: $0.02 USDC (20000 atomic)
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9

GET /api/portfolio

On-chain portfolio snapshot for one EVM address (?address=0x…, required).

  • Networks: Base + Ethereum mainnet (public RPCs; optional BASE_RPC_URL / ETH_RPC_URL with failover)
  • Tokens: native ETH; USDC, WETH, DAI on both chains; WBTC on Ethereum; cbBTC on Base (honest substitute — Base has no BitGo WBTC)
  • USD marks via Coinbase public exchange rates (CoinGecko for the rest or on Coinbase failure); real balances only — if an RPC fails, that network is marked failed in networks[] / warnings
  • Rule-based concentration score (0–100) and rule findings (largest asset weight vs 35%/50%, stablecoin share vs 15%/85%, largest chain share vs 85%, low native ETH gas balance; returned in the suggestions array as plain findings, no instructions) with transparent formulas in methodology
  • Price: $0.04 USDC (40000 atomic)
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9

GET /api/gas

Live gas snapshot for Base and Ethereum from public RPCs.

  • Prefers eth_feeHistory (last 20 blocks, reward percentiles 10/50/90): baseFeeGwei, priorityFeeGwei (p50), suggestedMaxFeeGwei (= 2×baseFee + priority)
  • Falls back to eth_gasPrice when feeHistory is unavailable
  • Transparent timingHint: cheap / normal / expensive from percentile rank of the latest confirmed baseFee within the feeHistory window (<33 / 33–67 / >67); documented in methodology — not a forecast
  • Optional ETH USD (Coinbase Exchange, CoinGecko fallback) for a simple 21k-gas transfer cost estimate (simpleTransfer.costUsd)
  • Price: $0.01 USDC (10000 atomic)
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9

GET /api/funding

Live perpetual funding rates for BTC / ETH / SOL from OKX (same source as /api/signals; Vercel-friendly — not Binance/Bybit).

  • Reuses lib/okx.ts (fetchAllFunding)
  • Transparent methodology on every response (including errors)
  • Optional crowding hint from funding sign + magnitude only (rules: quiet / mild / elevated / extreme; side longs/shorts/neutral) — not a forecast
  • Honesty: real OKX data only
  • Price: $0.01 USDC (10000 atomic) — cheap poll product for agents
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9

GET /api/fetch

Fetch a public http(s) URL (?url=https://…, required) and return best-effort clean text/markdown.

  • SSRF-safe: blocks localhost, private, link-local, and metadata IPs; re-checks each redirect hop
  • Caps: ~200KB raw body, 8s timeout, max 3 redirects
  • Preferred Content-Types: text/html, text/plain, application/json, text/markdown
  • Cleaning is best-effort (strip script/style/chrome, collapse whitespace) — not a full readability engine
  • Price: $0.02 USDC (20000 atomic)
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9

GET / POST /api/http

Universal agent HTTP proxy — call a public http(s) URL with optional method / headers / body; get back upstream status, filtered headers, and body (text or base64) plus contentType and elapsedMs.

  • Price choice: $0.01 USDC (10000 atomic) for volume. /api/fetch stays $0.02 (specialized clean-text). Prefer /api/http for raw proxy traffic.
  • Inputs: url (required); method (GET default; GET|POST|HEAD|PUT|PATCH|DELETE); optional allowlisted headers (no Cookie / hop-by-hop); optional body for POST/PUT/PATCH (size-capped)
  • GET query: ?url=&method=&headers=<json>; POST JSON body for full control including upstream body
  • SSRF-safe (shared guards with /api/fetch): blocks private/link-local/metadata; http/https only; re-checks redirects
  • Caps: ~384KB response body, 64KB request body, 12s timeout, max 3 redirects
  • Honesty: upstream 4xx/5xx returned in status when the hop succeeded; proxy failures use honest error codes
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET/POST → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9
  • Bazaar discovery advertises GET; POST is paid identically

GET / POST /api/extract

URL or HTML → structured page fields for agents (title, description, canonical, language, links, images, headings, json-ld, text sample).

  • Price choice: $0.015 USDC (15000 atomic) — between /api/http ($0.01) and /api/fetch ($0.02). Same payTo + Base USDC stack.
  • Inputs: url (optional if html provided) and/or html (size-capped). Prefer fetching url with the same SSRF-safe assertSafePublicUrl as fetch/http.
  • GET query: ?url=&fields=<url-encoded json>; POST JSON { url?, html?, fields? } for html payloads
  • Optional fields: up to 20 named CSS selectors, e.g. {"title":"h1","links":{"selector":"a.story","attr":"href","all":true,"limit":5}}. attr is text (default), html, or any attribute (href/src resolved to absolute URLs). Unmatched fields return null plus fieldErrors[name] (no_match, bad_selector, attr_missing, time_budget, document_too_deep, eval_failed). If none match: 422 no_fields_matched, not charged. Hard 2s selector budget; :has() at most once per selector; pages nested >256 levels are refused for fields. Deterministic selectors, no AI.
  • Lightweight regex parse (no cheerio). Empty fields omitted. Not a full browser DOM.
  • Caps: ~200KB HTML, 8s timeout, max 3 redirects (same band as /api/fetch)
  • No cookie jar. Private/localhost/link-local/metadata blocked.
  • runtime = 'nodejs', dynamic = 'force-dynamic'
  • Unpaid GET/POST → 402 + PAYMENT-REQUIRED (v2, eip155:8453) · payTo 0x5b32c973596078a967562ca652761404f19be0e9
  • Bazaar discovery advertises GET; POST is paid identically

GET /status

Public page showing the live USDC balance of the payTo address on Base (via public RPC / BASE_RPC_URL balanceOf) plus the live paid catalog and agent how-to.

  • On RPC failure the page shows an honest error — it does not fall back to a cached balance. Custody note: interim Coinbase-custodial payTo (not Safe/multisig); upgrade path later.
  • First settle noted as /api/pulse $0.005 (0xedbd1a51…); six crypto routes frozen; /api/fetch + /api/http + /api/extract deliberately LIVE.

Local development

Requires Node.js 20+ (tested on 20.19).

cd horizon-pulse
cp .env.example .env.local
# fill CDP_API_KEY_ID + CDP_API_KEY_SECRET for verify/settle
npm install
npm run dev
npm run build && npm start

Environment variables (Vercel / production)

Copy from .env.example:

VariableRequiredNotes
X402_NETWORKrecommendedbase
PAY_TOoptionaldefaults to 0x5b32c973596078a967562ca652761404f19be0e9
CDP_API_KEY_IDfor settleCoinbase Developer Platform
CDP_API_KEY_SECRETfor settlePKCS8 PEM (store safely; never commit)
BASE_RPC_URLoptionaloverrides default Base RPC for /status + /api/portfolio + /api/gas
ETH_RPC_URLoptionaloverrides default Ethereum RPC for /api/portfolio + /api/gas

Without CDP keys:

  • Unpaid GET and OPTIONS still return correct 402 / discovery with v2 PAYMENT-REQUIRED (payTo, eip155:8453, USDC amount) — no CDP required for discovery.
  • Requests that include PAYMENT-SIGNATURE (or legacy X-PAYMENT) receive 503 until CDP_API_KEY_ID + CDP_API_KEY_SECRET are set on Vercel (settlement path).
  • Do not expect live GET to 500 when CDP is missing; that was a prior bug fixed by gating withX402 behind payment + CDP credentials.

Stack

  • Next.js 16 (App Router) + TypeScript
  • @x402/next + @x402/core + @x402/evm + @x402/extensions
  • @coinbase/x402 for CDP facilitator auth headers
  • viem for treasury USDC balanceOf on Base, portfolio balances, and gas feeHistory on Base + Ethereum

Deploy notes

  1. Create the GitHub repo / remote https://github.com/horizon-pulse/horizon-pulse.git.
  2. Set Vercel env vars (above). Prefer PEM secret as a single line with \n escapes if the UI is single-line.
  3. Deploy from main. Ensure functions use Node.js runtime (routes already set export const runtime = 'nodejs').
  4. Smoke-test: curl -i https://horizonpulse.dev/api/pulse (or /api/yield, /api/portfolio, /api/gas, /api/funding, /api/fetch?url=https://example.com, /api/http?url=https://example.com, /api/extract?url=https://example.com) should return 402 with payment requirements pointing at the new payTo.
  5. Confirm /status shows the treasury balance for 0x5b32…e0e9.

Push (from this workspace)

This scaffold does not push for you (no credentials). From a machine with GitHub auth:

cd /workspace/horizon-pulse
git init
git add .
git commit -m "feat: Horizon Pulse x402 API with new Base treasury"
git branch -M main
git remote add origin https://github.com/horizon-pulse/horizon-pulse.git
git push -u origin main

If the remote already has history:

git remote add origin https://github.com/horizon-pulse/horizon-pulse.git
git fetch origin
git pull origin main --rebase   # or merge, as appropriate
git push -u origin main

Reference agent (examples/)

Unpaid 402 discovery for the live paid routes listed in the script, plus optional paid single-route when SMOKE_PRIVATE_KEY is set:

cd horizon-pulse
node examples/reference-agent.mjs
# optional paid:
SMOKE_PRIVATE_KEY=0x... SMOKE_ROUTE=/api/pulse node examples/reference-agent.mjs

See examples/README.md. Uses @x402/core + @x402/evm from this package. Never commit secrets.

Paid smoke test (/api/pulse)

End-to-end check against production (or SMOKE_BASE_URL): unpaid 402 → sign EIP-3009 / x402 with a local key → paid 200 + on-chain USDC toward payTo.

cd horizon-pulse
npm install   # if needed; uses existing @x402/* + viem
SMOKE_PRIVATE_KEY=0x... node scripts/smoke-pulse.mjs
# or: npm run smoke:pulse

Optional:

SMOKE_BASE_URL=https://horizonpulse.dev
# backup: https://horizon-pulse-seven.vercel.app
BASE_RPC_URL=https://mainnet.base.org

Never paste your private key in chat, commits, or screenshots. Export it only in your local shell (e.g. Mac mini). Without SMOKE_PRIVATE_KEY, the script exits with usage help (safe dry-run).

License

Private / as designated by the horizon-pulse org.

Reviews

No reviews yet

Be the first to review this server!