@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.
- package/README.md +25 -0
- package/index.js +57 -1
- 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
|
-
|
|
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.
|
|
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": "
|
|
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"
|