@forgemeshlabs/x402-ads-mcp 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/index.js +57 -1
  3. package/package.json +5 -2
package/README.md CHANGED
@@ -23,6 +23,18 @@ Wraps the [ForgeMesh x402 Ads & Intent Network](https://ads.forgemesh.io). Paid
23
23
 
24
24
  Both env vars are optional. With neither set, free tools work fully and paid tools return the x402 payment challenge (price, network, payTo) instead of settling — useful for inspection before spending anything.
25
25
 
26
+ ## Bonus: one-command publisher registration
27
+
28
+ This package doubles as the signup CLI for API operators (no MCP client needed):
29
+
30
+ ```bash
31
+ # read the terms first: https://ads.forgemesh.io/terms
32
+ WALLET_PRIVATE_KEY=0xYOUR_WALLET npx -y @forgemeshlabs/x402-ads-mcp register \
33
+ --url https://api.your-service.com --accept-terms
34
+ ```
35
+
36
+ One $0.10 USDC payment on Base; the paying wallet becomes your identity and your publisher key is printed once. Invalid requests are rejected **before** payment — you can't pay for a doomed registration.
37
+
26
38
  ## Tools
27
39
 
28
40
  | Tool | Price | What it returns |
@@ -48,6 +60,19 @@ Both env vars are optional. With neither set, free tools work fully and paid too
48
60
 
49
61
  Use a dedicated hot wallet holding only small working balances. The key never leaves your machine — payments are signed locally (EIP-3009) and settle on-chain.
50
62
 
63
+ Ready-made configs live in [`examples/`](./examples): a Claude Desktop `mcpServers` block and a commented env-var template.
64
+
65
+ ## Testing (safe by construction)
66
+
67
+ ```bash
68
+ npm test # smoke: MCP boots over stdio and lists its 7 tools
69
+ npm run test:free # free tools against the live network
70
+ npm run test:challenge # every paid tool returns an x402 challenge — no wallet, nothing can spend
71
+ npm run test:all # all of the above
72
+ ```
73
+
74
+ No test settles a payment. The challenge test deletes the payment env vars before loading, so it cannot move funds even if your shell has a wallet configured.
75
+
51
76
  ## The network in one sentence
52
77
 
53
78
  **We measure machine commerce, not API content** — publishers running the [`@forgemeshlabs/x402-ads`](https://www.npmjs.com/package/@forgemeshlabs/x402-ads) middleware contribute anonymized 402 probe metadata; this MCP sells the aggregate demand signal back to agents and builders.
package/index.js CHANGED
@@ -266,8 +266,64 @@ async function main() {
266
266
  process.stdin.on("end", () => clearInterval(keepAlive));
267
267
  }
268
268
 
269
+ // `npx -y @forgemeshlabs/x402-ads-mcp register` — one-command publisher signup.
270
+ async function registerCli(argv) {
271
+ const args = { categories: [] };
272
+ for (let i = 0; i < argv.length; i++) {
273
+ if (argv[i] === "--url") args.service_url = argv[++i];
274
+ else if (argv[i] === "--name") args.name = argv[++i];
275
+ else if (argv[i] === "--category") args.categories.push(argv[++i]);
276
+ else if (argv[i] === "--contact") args.contact = argv[++i];
277
+ else if (argv[i] === "--accept-terms") args.terms_accepted = true;
278
+ }
279
+ if (!args.service_url || !args.terms_accepted) {
280
+ console.error("Usage: WALLET_PRIVATE_KEY=0x... x402-ads-mcp register --url https://api.your-service.com --accept-terms");
281
+ console.error(" [--name \"My API\"] [--category weather] [--contact you@example.com]");
282
+ console.error("");
283
+ console.error("Registers you as a publisher for one $0.10 USDC x402 payment on Base.");
284
+ console.error("The paying wallet becomes your identity; your key is printed once.");
285
+ console.error("--accept-terms is required — read them first: " + BASE_URL + "/terms");
286
+ process.exit(1);
287
+ }
288
+ const httpClient = walletClient();
289
+ if (!httpClient) {
290
+ console.error("WALLET_PRIVATE_KEY is required: a Base mainnet wallet holding at least $0.10 USDC.");
291
+ process.exit(1);
292
+ }
293
+ if (!args.name) delete args.name;
294
+ if (!args.contact) delete args.contact;
295
+ if (!args.categories.length) delete args.categories;
296
+
297
+ const url = BASE_URL + "/v1/publishers/register";
298
+ const init = { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(args) };
299
+ const res = await fetch(url, init);
300
+ if (res.status === 400) {
301
+ const body = await res.json().catch(() => ({}));
302
+ console.error("Rejected before payment (nothing was charged): " + (body.error || res.statusText));
303
+ process.exit(1);
304
+ }
305
+ if (res.status !== 402) throw new Error(`expected x402 challenge, got ${res.status}`);
306
+ let challengeBody;
307
+ try {
308
+ challengeBody = await res.clone().json();
309
+ } catch (_) {}
310
+ const paymentRequired = httpClient.getPaymentRequiredResponse((n) => res.headers.get(n), challengeBody);
311
+ const paymentPayload = await createChainTimedPaymentPayload(httpClient, paymentRequired);
312
+ const paidRes = await fetch(url, { ...init, headers: { ...init.headers, ...httpClient.encodePaymentSignatureHeader(paymentPayload) } });
313
+ const out = await paidRes.json().catch(() => ({}));
314
+ if (!paidRes.ok) throw new Error(`registration failed (${paidRes.status}): ${JSON.stringify(out).slice(0, 240)}`);
315
+ console.log("Registered: " + out.publisher_id);
316
+ console.log("");
317
+ console.log("Your publisher key (shown once — store it now):");
318
+ console.log(" " + out.publisher_key);
319
+ console.log("");
320
+ console.log("Next: set it as X402_ADS_PUBLISHER_KEY in your server env and mount the");
321
+ console.log("@forgemeshlabs/x402-ads middleware. Your own traffic reports are free.");
322
+ }
323
+
269
324
  if (require.main === module) {
270
- main().catch((error) => {
325
+ const run = process.argv[2] === "register" ? registerCli(process.argv.slice(3)) : main();
326
+ run.catch((error) => {
271
327
  console.error(error instanceof Error ? error.message : String(error));
272
328
  process.exit(1);
273
329
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgemeshlabs/x402-ads-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "mcpName": "io.github.forgemeshlabs/x402-ads-mcp",
5
5
  "description": "Machine-commerce intent analytics over x402: what autonomous agents probe, want, and abandon across the x402 ecosystem — trends, category demand, and why-agents-didn't-buy funnels, paid per call in USDC on Base. Free for publishers on their own traffic.",
6
6
  "main": "index.js",
@@ -18,7 +18,10 @@
18
18
  "scripts": {
19
19
  "start": "node index.js",
20
20
  "check": "node --check index.js",
21
- "test": "npm run check && node scripts/test-mcp.js"
21
+ "test": "node tests/smoke.test.js",
22
+ "test:free": "node tests/free-tools.test.js",
23
+ "test:challenge": "node tests/paid-challenge.test.js",
24
+ "test:all": "npm run check && npm test && npm run test:free && npm run test:challenge"
22
25
  },
23
26
  "engines": {
24
27
  "node": ">=18"