x402dispatcher
README.md
<img src="https://github.com/jegamboafuentes/x402dispatcher/blob/main/public/images/402dispatcherLogo.png?raw=true" alt="402 dispatcher logo" width="50%">
[](https://smithery.ai/servers/metaverse-professional/dispatcher402)
**Live:** [https://402dispatcher.com/](https://402dispatcher.com/)
**GitHub:** [https://github.com/jegamboafuentes/x402dispatcher](https://github.com/jegamboafuentes/x402dispatcher)
**Glama:** [https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher](https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher)
**Smithery:** [https://smithery.ai/servers/metaverse-professional/dispatcher402](https://smithery.ai/servers/metaverse-professional/dispatcher402)
Cloud-hosted **x402 Bazaar Aggregator** for AI agents: discover paid APIs from the Coinbase x402 Bazaar, wrap them as Model Context Protocol (MCP) tools, settle micropayments from a CDP treasury wallet, and return the upstream data to the agent.
This repo is currently at **V11** (public registry distribution). Custom domain, operator console, and inbound paywall are live on Base mainnet.
| Surface | URL |
|---|---|
| Operator console | https://402dispatcher.com/ |
| MCP (Streamable HTTP) | https://402dispatcher.com/mcp |
| Glama connector | https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher |
| Smithery | https://smithery.ai/servers/metaverse-professional/dispatcher402 |
| Health | https://402dispatcher.com/health |
| Agent discovery | https://402dispatcher.com/.well-known/agent.json |
| LLM brief | https://402dispatcher.com/llms.txt |
---
## Why this exists
AI agents are good at reasoning and tool use, but bad at paying for APIs. The [x402](https://x402.org) protocol turns HTTP `402 Payment Required` into a programmable stablecoin micropayment rail (typically USDC).
**x402dispatcher** sits in the middle as a solo-operator friendly aggregator:
| Idea | What it means |
|---|---|
| Discovery | Query the public Coinbase x402 Bazaar catalog |
| MCP integration | Expose discovered APIs as MCP tools for Cursor / agents |
| Dispatch | Sign and settle payment from a treasury wallet via `@coinbase/cdp-sdk` |
| Monetization | Apply a micro-markup on top of upstream cost and retain the spread |
Funds move on-chain (wallet → seller / Merchant). The platform does not custody buyer card data. Until V6, the **operator Treasury** funds outbound calls; after V6, **calling agents** pay inbound first.
<img src="https://github.com/jegamboafuentes/x402dispatcher/blob/main/public/images/402dispatcherright.png?raw=true" alt="402 dispatcher logo" width="50%">
---
## Roadmap
| Version | Status | Goal |
|---|---|---|
| **V1** | Done | Manually wrap one paid-style flow (MBTA demo + $0.01 USDC testnet settle) |
| **V2** | Done | Auto-discover Base Sepolia Bazaar APIs and wrap many as MCP tools with real x402 payment |
| **V3** | Done | Smart arbitrage: search, compare prices, pick cheapest API for a task (with failover) |
| **V4** | Done | Track success/latency; economy vs verified routing tiers |
| **V5** | Done | Cloud Run + `agent.json` + HTTP MCP; `X402_ENV` for Sepolia vs Base mainnet |
| **V6** | Done | **Inbound paywall** — calling agents pay Merchant before upstream proxy |
| **V7** | Done | Cashflow ledger (money in / out / markup) via API + MCP |
| **V8** | Done | Operator monitoring UI (Linux console at `/`) + branding/SEO |
| **V9** | Done | Custom domain [402dispatcher.com](https://402dispatcher.com/) for public MCP / Cloud Run |
| **V10** | Done | Durable SQLite ledger (GCS-backed on Cloud Run) so PnL/settlements survive restarts |
| **V11** | **Current** | Public distribution — Glama + Smithery registry listings, slim MCP tool surface |
| **V12** | Planned | Broader discovery: Official MCP Registry, PulseMCP, BusinessMCP, GitHub Awesome lists |
You do **not** need a UI for agents — MCP + `agent.json` is the product surface. The UI at `/` is for **you** (operator monitoring).
---
## What V11 does
Public MCP distribution:
| Registry | Listing |
|---|---|
| Glama | https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher |
| Smithery | https://smithery.ai/servers/metaverse-professional/dispatcher402 |
The public MCP surface stays small (discovery + route/call + operator tools). Per-API Bazaar tools stay off unless `EXPOSE_DYNAMIC_BAZAAR_TOOLS=true`.
### Planned V12
| Target | Notes |
|---|---|
| Official MCP Registry | https://modelcontextprotocol.io/registry |
| PulseMCP | Directory listing |
| BusinessMCP | Directory listing |
| GitHub Awesome lists | Community MCP / agentic lists |
---
## What V10 does
Cloud Run `/tmp` is wiped on new revisions, which is why the console PnL and settlements vanished while **wallet balances stayed** (those are on-chain).
V10 stores cashflow + API stats in **SQLite** (`DATA_DIR/ledger.sqlite`). On Cloud Run the file is restored/saved to a GCS bucket (`GCS_DATA_BUCKET`) after each write.
| Where | Storage |
|---|---|
| Local | `data/ledger.sqlite` |
| Cloud Run | `/tmp/.../ledger.sqlite` + `gs://x402dispatcher-data-…/ledger.sqlite` |
| Wallets | Base chain (not in this DB) |
```bash
npm run test:v10
```
---
## What V9 does
Production traffic is served on the custom domain:
- **Site / console:** https://402dispatcher.com/
- **MCP:** https://402dispatcher.com/mcp
- **Repo:** https://github.com/jegamboafuentes/x402dispatcher
Cloud Run remains the backend (`x402dispatcher` in `us-central1`); DNS + HTTPS map to that service.
---
## What V8 does
V8 adds a **Linux console–style operator dashboard** at `/`:
| Panel | Source |
|---|---|
| Wallets | `GET /v1/wallets` — Treasury + Merchant USDC/ETH on-chain balances |
| PnL | `GET /v1/pnl` — revenue / cogs / markup / gross profit |
| Recent settlements | `GET /v1/cashflow` — latest ledger rows |
Auto-refreshes every 5s. Credits in the footer: run by [metaverseprofessional.tech](https://metaverseprofessional.tech/), developed by [enriquegamboa.info](https://enriquegamboa.info/).
```bash
npm run test:v8
```
Open locally: `http://127.0.0.1:8080/` (with `npm run start:http`).
Production: https://402dispatcher.com/
---
## Connecting MCP clients (Cursor / ChatGPT / Gemini / Claude)
Point a Streamable HTTP MCP client at:
```text
https://402dispatcher.com/mcp
```
Or install from a registry:
- [Glama connector](https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher)
- [Smithery](https://smithery.ai/servers/metaverse-professional/dispatcher402)
Discovery documents for agents and crawlers:
| File | Purpose |
|---|---|
| https://402dispatcher.com/.well-known/agent.json | Machine-readable agent / MCP capabilities |
| https://402dispatcher.com/llms.txt | Short LLM-oriented summary (llms.txt convention) |
| https://402dispatcher.com/robots.txt | Crawl rules + sitemap |
| https://glama.ai/mcp/connectors/com.402dispatcher/402-dispatcher | Glama MCP registry listing |
| https://smithery.ai/servers/metaverse-professional/dispatcher402 | Smithery MCP registry listing |
| https://github.com/jegamboafuentes/x402dispatcher | Source + docs for tool registries |
Paid tools require an x402-capable wallet client (inbound USDC to Merchant). Free tools (`quote_route`, `search_bazaar`, `get_pnl`, …) work without payment.
---
## What V7 does
V7 persists a **cashflow ledger** so you can audit solo-business money movement:
| Direction | Meaning |
|---|---|
| `in` | Calling agent → Merchant (inbound x402) |
| `out` | Treasury → upstream seller |
| `markup` | Treasury → Merchant (spread transfer) |
Tools (free / operator): `get_cashflow`, `get_pnl`. HTTP: `GET /v1/cashflow`, `GET /v1/pnl`.
Stored under SQLite `DATA_DIR/ledger.sqlite` (V10). Cloud Run also mirrors the file to GCS so it survives restarts.
```bash
npm run test:v7
```
---
## What V6 does
Calling agents must pay **you** (Merchant) before the proxy spends Treasury:
| Tool | Free / Paid |
|---|---|
| `quote_route`, `search_bazaar`, `list_*`, `get_api_stats`, `get_paywall_status` | Free |
| `route_and_call`, `call_x402_api` | **Paid inbound** |
Flow:
1. Agent calls a paid tool
2. Server challenges with x402 (price = `INBOUND_PRICE_USD`, default `MAX_PRICE_USD`)
3. Agent settles USDC → **Merchant**
4. Proxy pays upstream from **Treasury** and returns data
Env:
```env
INBOUND_PAYWALL=true # set false to disable (operator-only treasury spend)
INBOUND_PRICE_USD=0.01 # flat inbound fee (clamped to MAX_PRICE_USD)
X402_ENV=development|production
# X402_PAY_TO=0x... # optional Merchant override
```
### Test V6 locally (Sepolia)
```bash
# terminal 1
$env:X402_ENV='development'; $env:INBOUND_PAYWALL='true'; npm run start:http
# terminal 2 — first run prints Buyer address; fund with Sepolia USDC
$env:X402_ENV='development'; $env:PUBLIC_BASE_URL='http://127.0.0.1:8080'; npm run test:v6
```
### Test V6 on production (real USDC)
```bash
$env:X402_ENV='production'; $env:PUBLIC_BASE_URL='https://402dispatcher.com'; npm run test:v6
```
Fund the printed **Buyer** CDP account with Base mainnet USDC. Expect: free `quote_route` OK, then paid `route_and_call` with inbound settlement + upstream weather.
---
## What V5 does
V5 exposes the same dispatcher over the public internet:
| Surface | Path |
|---|---|
| Operator console | `GET /` |
| Health | `GET /health` |
| Agent discovery | `GET /.well-known/agent.json` |
| MCP (Streamable HTTP) | `ALL /mcp` |
| Local stdio (Cursor) | `npm start` |
Deploy target: **GCP Cloud Run** project `experiment-jegf-personal`, public domain **https://402dispatcher.com/**.
```bash
npm run start:http # local HTTP on :8080
npm run deploy:gcp # build + deploy to Cloud Run
$env:PUBLIC_BASE_URL='https://402dispatcher.com'; npm run test:v5
```
---
## What V4 does
On top of V3 routing, V4 records every paid call’s success and latency in `data/api-stats.json`, then offers two tiers:
| Tier | Behavior |
|---|---|
| `economy` | Cheapest first (V3 behavior) |
| `verified` | Only APIs with enough successful history; ranked by reliability/latency/price score |
Thresholds (env): `VERIFIED_MIN_SAMPLES` (default `2`), `VERIFIED_MIN_SUCCESS_RATE` (default `0.8`).
New tools: `get_api_stats`, `list_verified_apis`. `quote_route` / `route_and_call` accept optional `tier`.
---
## What V3 does
On top of V2 discovery + payment, V3 adds a router:
1. **`quote_route`** — search Bazaar for a natural-language task, rank candidates by **total price** (upstream + markup), return the plan **without paying**
2. **`route_and_call`** — same ranking, pay and call the cheapest; on failure, try the next-cheapest (up to `max_attempts`)
All spends remain gated by `MAX_PRICE_USD`.
---
## What V2 does
On startup the MCP server:
1. Loads credentials from `.env`
2. Resolves a CDP **Treasury** payer wallet
3. Searches / lists the Coinbase Bazaar for paid HTTP resources priced at or below `MAX_PRICE_USD`
4. Caches matches for `search_bazaar` / `quote_route` / `call_x402_api` / `route_and_call`
5. Registers a small fixed tool surface (not one tool per Bazaar API by default)
When an agent calls `route_and_call` or `call_x402_api`:
1. Enforce `MAX_PRICE_USD` on **upstream price + markup**
2. Pay the real x402 endpoint with `CdpX402Client` + `wrapFetchWithPayment` from `@x402/fetch`
3. Collect the markup spread (Treasury → Merchant USDC transfer when possible)
4. Return `{ payment, data }` to the agent
Set `EXPOSE_DYNAMIC_BAZAAR_TOOLS=true` only if you want the legacy one-tool-per-API flood (hurts Glama TDQS).
---
## Architecture
<img src="https://github.com/jegamboafuentes/x402dispatcher/blob/main/public/images/402distpatcher-architecture.png?raw=true" alt="402 dispatcher logo">
```
Agent / Cursor
│ MCP (stdio)
▼
x402dispatcher MCP server (src/index.ts)
│
├─ Discovery → listX402DiscoveryResources / searchX402Resources (@coinbase/cdp-sdk)
├─ Payment → CdpX402Client + wrapFetchWithPayment (@coinbase/cdp-sdk/x402, @x402/fetch)
├─ Routing → economy (price) / verified (stats score) with failover
├─ Stats → data/api-stats.json success + latency history
├─ Guardrails → MAX_PRICE_USD (+ SDK spend controls)
└─ Markup → MARKUP_BPS applied; optional USDC transfer to Merchant account
│
▼
Upstream x402 HTTP API (Bazaar listing)
```
### Key packages
- `@coinbase/cdp-sdk` — wallets, Bazaar discovery, `CdpX402Client`
- `@x402/fetch` / `@x402/core` / `@x402/evm` — HTTP 402 payment loop
- `@modelcontextprotocol/sdk` — MCP server + tools
- `dotenv`, `zod`, `viem`
---
## Requirements
- **Node.js 19+** (CDP SDK requirement; **22 LTS recommended**)
- Coinbase Developer Platform credentials:
- `CDP_API_KEY_ID`
- `CDP_API_KEY_SECRET`
- `CDP_WALLET_SECRET` (Wallet Secret from CDP Portal → Non-custodial Wallet → Security — **not** a MetaMask private key)
- Base **mainnet** USDC on the Treasury address for production (`X402_ENV=production`), or Base Sepolia test USDC for development (default)
---
## Production (real USDC on Base)
1. Fund your CDP **`Treasury`** wallet with **USDC on Base mainnet** (not Sepolia).
2. Set in `.env` (local) or Cloud Run env:
```env
X402_ENV=production
MAX_PRICE_USD=0.01
```
3. Redeploy (Cloud Run defaults to production):
```bash
npm run deploy:gcp
```
4. Confirm `/health` shows `"x402_env": "production"` and `"network": "eip155:8453"`.
To stay on testnet locally, omit `X402_ENV` or set `X402_ENV=development`.
---
## Setup
```bash
git clone https://github.com/jegamboafuentes/x402dispatcher.git
cd x402dispatcher
npm install
cp .env.example .env
# edit .env with your CDP credentials
```
### Environment variables
| Variable | Required | Description |
|---|---|---|
| `CDP_API_KEY_ID` | Yes | CDP API key ID |
| `CDP_API_KEY_SECRET` | Yes | CDP API key secret |
| `CDP_WALLET_SECRET` | Yes | CDP Wallet Secret (base64 P-256 key from Portal) |
| `MAX_PRICE_USD` | Recommended | Hard cap before any automated spend (e.g. `0.01`) |
| `MARKUP_BPS` | Optional | Markup in basis points (default `1000` = 10%) |
| `DISCOVERY_LIMIT` | Optional | Max Bazaar APIs to warm/cache at startup (default `40`, max `100`) |
| `EXPOSE_DYNAMIC_BAZAAR_TOOLS` | Optional | `true` registers one MCP tool per discovered API (default `false`) |
| `VERIFIED_MIN_SAMPLES` | Optional | Min successful-history calls for Verified (default `2`) |
| `VERIFIED_MIN_SUCCESS_RATE` | Optional | Min success rate 0–1 for Verified (default `0.8`) |
| `X402_ENV` | Optional | `development` (Base Sepolia, default) or `production` (Base mainnet, real USDC) |
| `INBOUND_PAYWALL` | Optional | V6: `true` (default) to charge callers; `false` for treasury-only operator mode |
| `INBOUND_PRICE_USD` | Optional | V6 flat inbound fee (default = `MAX_PRICE_USD`) |
| `X402_PAY_TO` | Optional | Override Merchant receive address for inbound payments |
| `GCS_DATA_BUCKET` | Cloud Run | V10: GCS bucket that stores `ledger.sqlite` across restarts |
| `GCS_DATA_OBJECT` | Optional | Object name (default `ledger.sqlite`) |
| `CDP_PRIVATE_KEY` | Optional | Only if you import a specific EOA into CDP (not used by default V2+ payer path) |
Never commit `.env`. Only `.env.example` is tracked.
### Fund the treasury
```bash
npx tsx -e "import 'dotenv/config'; import { CdpX402Client } from '@coinbase/cdp-sdk/x402'; const c = new CdpX402Client({ environment: 'development', walletConfig: { type: 'eoa', accountName: 'Treasury' } }); console.log(await c.getAddresses());"
```
Send Base Sepolia **USDC** (and a little **ETH**) to the printed `evmAddress`.
---
## Run
### Local MCP (stdio — Cursor)
```bash
npm start
```
### Local HTTP MCP (V5)
```bash
npm run start:http
```
Then open `http://127.0.0.1:8080/health` and `http://127.0.0.1:8080/.well-known/agent.json`.
### Deploy to GCP Cloud Run
```bash
npm run deploy:gcp
```
Uses project `experiment-jegf-personal`, region `us-central1`, service `x402dispatcher`. Secrets are read from local `.env` on first create (`CDP_*`, `MAX_PRICE_USD`).
### Cursor MCP config
Project file: `.cursor/mcp.json` (already included). Cursor should spawn:
```json
{
"mcpServers": {
"x402dispatcher": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"cwd": "${workspaceFolder}"
}
}
}
```
Reload MCP in Cursor after clone/install. If `${workspaceFolder}` is not expanded on your Cursor build, set `cwd` to the absolute path of this repo and optionally point `command` at your Node 22 binary.
---
## MCP tools
### Core
| Tool | Purpose |
|---|---|
| `quote_route` | Rank matching APIs; `tier=economy\|verified`; no payment |
| `route_and_call` | Pay/call best match for tier; failover; records stats |
| `get_api_stats` | **V4** — local success/latency history |
| `list_verified_apis` | **V4** — APIs that currently qualify as Verified |
| `search_bazaar` | Semantic/text search of Base Sepolia Bazaar APIs under `MAX_PRICE_USD` |
| `list_discovered_apis` | List APIs currently cached/registered |
| `call_x402_api` | Pay + call by `tool_name` or full resource URL |
### Dynamic tools (optional)
By default, x402dispatcher does **not** register one MCP tool per Bazaar resource (keeps Glama / clients at a small tool count). Discovery still works via `search_bazaar` → `call_x402_api` or `route_and_call`. Set `EXPOSE_DYNAMIC_BAZAAR_TOOLS=true` to restore the legacy flood of `x402_<host>_<path>_<n>` tools.
---
## Testing
### V5 HTTP (cloud or local)
With `npm run start:http` running locally (or after deploy):
```bash
npm run test:v5
# or against production:
$env:PUBLIC_BASE_URL='https://402dispatcher.com'; npm run test:v5
```
Expect: `V5 HTTP SMOKE TEST PASSED`
### V4 end-to-end
Seeds two economy weather calls, promotes the winner into Verified, then quotes/routes with `tier=verified`:
```bash
npm run test:v4
```
Expect: `V4 SMOKE TEST PASSED`
### Earlier versions
```bash
npm run test:v3
npm run test:v2
```
### Manual checks in Cursor
1. Reload the `x402dispatcher` MCP server
2. Ask for weather with economy routing a couple of times (builds stats)
3. Ask: “List verified APIs” / “Get API stats”
4. Ask: “Use the verified tier to get weather for Boston”
5. Confirm `chosen.verified` is true and `data/api-stats.json` grew
### Guardrail check
Set `MAX_PRICE_USD` below a listing’s total and confirm quote/route refuse or return zero candidates.
---
## Project layout
```
x402dispatcher/
├── src/
│ ├── index.ts # MCP server, tool registration
│ ├── discovery.ts # Bazaar list/search → DiscoveredApi
│ ├── payment.ts # CdpX402Client, markup, MBTA settle
│ ├── routing.ts # quote + economy/verified route + failover
│ ├── stats.ts # V4 local success/latency store
│ └── config.ts # MAX_PRICE_USD, MARKUP_BPS, verified thresholds
├── scripts/
│ ├── v4-smoke-test.ts
│ ├── v3-smoke-test.ts
│ ├── v2-smoke-test.ts
│ ├── mcp-test.ts
│ └── smoke-test.ts
├── data/ # local api-stats.json (gitignored)
├── .cursor/
│ ├── mcp.json
│ └── rules/ # security + x402-stack agent rules
├── AGENTS.md # product / roadmap context for agents
├── .env.example
└── package.json
```
---
## Security notes
- Wallet credentials load **only** from `.env` — never hardcode secrets.
- Every automated spend is gated by **`MAX_PRICE_USD`** before signing.
- V2 also configures CDP x402 **spend controls** (`maxAmountPerPayment` + Base Sepolia network allowlist).
- Treat the Bazaar as a catalog, not an endorsement. Prefer small caps on testnet first.
- `CDP_WALLET_SECRET` must be the Portal Wallet Secret (long base64), not a MetaMask hex key.
---
## Stack references
- [Coinbase CDP docs](https://docs.cdp.coinbase.com/)
- [Discover x402 services (Bazaar)](https://docs.cdp.coinbase.com/x402/buyer/discover-services)
- [x402 buyer quickstart](https://docs.cdp.coinbase.com/x402/buyer/quickstart)
- [Model Context Protocol](https://modelcontextprotocol.io/)
---
## License
ISC
<img src="https://github.com/jegamboafuentes/x402dispatcher/blob/main/public/images/402dispatcherMascot.png?raw=true" alt="402 dispatcher logo" width="50%">
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues