@forgemeshlabs/x402-ads-mcp 0.2.7 → 0.2.8
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 +1 -1
- package/glama.json +1 -1
- package/index.js +9 -9
- package/package.json +1 -1
- package/server.json +2 -2
- package/x402-guard.js +18 -2
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ One $0.10 USDC payment on Base; the paying wallet becomes your identity and your
|
|
|
49
49
|
| `get_terms` | free | Canonical terms + complete data-collection disclosure |
|
|
50
50
|
| `get_network_stats` | $0.005 | Network totals + monitor/indexer/agent classification split |
|
|
51
51
|
| `get_intent_trends` | $0.01 | Top endpoints & categories autonomous agents request |
|
|
52
|
-
| `get_category_demand` | $0.02 | Demand depth for one category:
|
|
52
|
+
| `get_category_demand` | $0.02 | Demand depth for one category: probes, agent probes, conversions, price points (404 no_data is not charged) |
|
|
53
53
|
| `get_intent_report` | $0.05 / **free*** | Why-agents-didn't-buy funnel for one service |
|
|
54
54
|
|
|
55
55
|
\* `get_intent_report` is free with `X402_ADS_PUBLISHER_KEY` for services you contribute events to — the data co-op rule: your own data is free, forever.
|
package/glama.json
CHANGED
package/index.js
CHANGED
|
@@ -32,7 +32,7 @@ const TOOL_SCHEMAS = {
|
|
|
32
32
|
limit: z.number().int().min(1).max(100).optional().describe("Max rows, 1-100 (default 20)"),
|
|
33
33
|
},
|
|
34
34
|
get_category_demand: {
|
|
35
|
-
category: z.string().min(1).max(60).regex(ID_PATTERN).describe("Category
|
|
35
|
+
category: z.string().min(1).max(60).regex(ID_PATTERN).describe("Category as reported by publishers, e.g. blockchain, developer-tools (get_intent_trends lists categories with data)"),
|
|
36
36
|
window: z.enum(WINDOWS).optional().describe("Time window: 24h, 7d, 30d, or all (default 30d)"),
|
|
37
37
|
},
|
|
38
38
|
get_intent_report: {
|
|
@@ -89,7 +89,7 @@ const TOOLS = [
|
|
|
89
89
|
openWorldHint: true,
|
|
90
90
|
},
|
|
91
91
|
description:
|
|
92
|
-
"Paid, $0.005 USDC on Base via x402. x402 network traffic stats: network-wide totals (events, services), traffic
|
|
92
|
+
"Paid, $0.005 USDC on Base via x402. x402 network traffic stats, no parameters: network-wide totals (x402 events, services), a breakdown of traffic by user-agent class (agent, browser, bot) and ad_activity. Use it to benchmark your service's agent-traffic share. Without WALLET_PRIVATE_KEY, returns the x402 payment challenge instead of settling.",
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
name: "get_intent_trends",
|
|
@@ -101,7 +101,7 @@ const TOOLS = [
|
|
|
101
101
|
openWorldHint: true,
|
|
102
102
|
},
|
|
103
103
|
description:
|
|
104
|
-
"Paid, $0.01 USDC on Base via x402. x402 agent demand trends: the most-requested x402 endpoints and categories
|
|
104
|
+
"Paid, $0.01 USDC on Base via x402. x402 agent demand trends: the most-requested x402 endpoints (service, endpoint, probes, agent_probes) and the categories with data, in a time window (24h, 7d, 30d or all; limit 1-100). Without WALLET_PRIVATE_KEY, returns the x402 payment challenge instead of settling.",
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
name: "get_category_demand",
|
|
@@ -113,7 +113,7 @@ const TOOLS = [
|
|
|
113
113
|
openWorldHint: true,
|
|
114
114
|
},
|
|
115
115
|
description:
|
|
116
|
-
"Paid, $0.02 USDC on Base via x402. x402 category demand depth
|
|
116
|
+
"Paid, $0.02 USDC on Base via x402. x402 category demand depth for one API category: summary (probes, agent_probes, conversions), price points probed and a daily series. The category must be one publishers report (get_intent_trends lists categories that have data); a category with no reported traffic in the window answers 404 no_data and is not charged. Without WALLET_PRIVATE_KEY, returns the x402 payment challenge instead of settling.",
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
name: "get_intent_report",
|
|
@@ -125,7 +125,7 @@ const TOOLS = [
|
|
|
125
125
|
openWorldHint: true,
|
|
126
126
|
},
|
|
127
127
|
description:
|
|
128
|
-
"x402 conversion funnel report for one service (why agents probe but do not pay):
|
|
128
|
+
"x402 conversion funnel report for one service (why agents probe but do not pay): funnel (total_probes, bounces, paid, conversion_rate), traffic_classes and top_abandoned_endpoints. Data exists only for services that report through the publisher middleware; a service with no events in the window answers 404 no_data and is not charged. FREE with X402_ADS_PUBLISHER_KEY for your registered services (the service id is bound to your key at registration); otherwise $0.05 USDC on Base via x402. Without a publisher key or WALLET_PRIVATE_KEY, returns the x402 payment challenge.",
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
name: "get_terms",
|
|
@@ -186,7 +186,7 @@ async function paidGet(path) {
|
|
|
186
186
|
const viaPublisherKey = !!process.env.X402_ADS_PUBLISHER_KEY;
|
|
187
187
|
return { paid: false, ...(viaPublisherKey ? { free_via_publisher_key: true } : {}), data: JSON.parse(res.text) };
|
|
188
188
|
}
|
|
189
|
-
if (res.status !== 402) throw
|
|
189
|
+
if (res.status !== 402) throw guard.httpError(res, `GET ${path} failed: `, false); // nothing was signed, so nothing was charged
|
|
190
190
|
|
|
191
191
|
const httpClient = walletClient();
|
|
192
192
|
if (!httpClient) {
|
|
@@ -210,7 +210,7 @@ async function paidGet(path) {
|
|
|
210
210
|
|
|
211
211
|
async function freeGet(path, asText = false) {
|
|
212
212
|
const res = await guard.fetchBounded(path);
|
|
213
|
-
if (!res.ok) throw
|
|
213
|
+
if (!res.ok) throw guard.httpError(res, `GET ${path} failed: `, false);
|
|
214
214
|
return asText ? res.text : JSON.parse(res.text);
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -238,7 +238,7 @@ async function callTool(name, args = {}) {
|
|
|
238
238
|
category: args.category,
|
|
239
239
|
}),
|
|
240
240
|
});
|
|
241
|
-
if (!res.ok) throw
|
|
241
|
+
if (!res.ok) throw guard.httpError(res, "POST /v1/decide failed: ", false);
|
|
242
242
|
return JSON.parse(res.text);
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -280,7 +280,7 @@ for (const tool of TOOLS) {
|
|
|
280
280
|
} catch (error) {
|
|
281
281
|
return {
|
|
282
282
|
isError: true,
|
|
283
|
-
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
283
|
+
content: [{ type: "text", text: error && error.status !== undefined ? JSON.stringify({ error: error.message, status: error.status, ...(typeof error.charged === "boolean" ? { charged: error.charged } : {}), upstream: error.upstream, untrusted: true }) : error instanceof Error ? error.message : String(error) }],
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
286
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgemeshlabs/x402-ads-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"mcpName": "io.github.forgemeshlabs/x402-ads-mcp",
|
|
5
5
|
"description": "Install one middleware. Monetize unused 402 responses with recommendations, discovery, and machine commerce analytics. MCP server: 7 tools for agent demand analytics, paid per call in USDC on Base; free for publishers on their own traffic.",
|
|
6
6
|
"main": "index.js",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/forgemeshlabs/x402-ads-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.8",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@forgemeshlabs/x402-ads-mcp",
|
|
14
|
-
"version": "0.2.
|
|
14
|
+
"version": "0.2.8",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/x402-guard.js
CHANGED
|
@@ -99,6 +99,21 @@ function createGuard(opts) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Keep the service's refusal intact: HTTP status, charged flag, and the body in err.upstream only.
|
|
103
|
+
// The message is short and fixed; upstream content is untrusted and never interpolated into prose.
|
|
104
|
+
function httpError(res, prefix, charged) {
|
|
105
|
+
let upstream;
|
|
106
|
+
try { upstream = JSON.parse(res.text); } catch { upstream = res.text.slice(0, 500); }
|
|
107
|
+
if (typeof upstream !== "string" && JSON.stringify(upstream).length > 1500) upstream = JSON.stringify(upstream).slice(0, 1500);
|
|
108
|
+
const code = upstream && typeof upstream === "object" && typeof upstream.error === "string" && /^[A-Za-z0-9_.:-]{1,64}$/.test(upstream.error) ? `: ${upstream.error}` : "";
|
|
109
|
+
const err = new Error(`${prefix}HTTP ${res.status}${code}`);
|
|
110
|
+
err.status = res.status;
|
|
111
|
+
err.upstream = upstream;
|
|
112
|
+
if (charged !== undefined) err.charged = charged;
|
|
113
|
+
else if (upstream && typeof upstream === "object" && typeof upstream.charged === "boolean") err.charged = upstream.charged;
|
|
114
|
+
return err;
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
function parseJson(res) {
|
|
103
118
|
try { return JSON.parse(res.text); } catch { throw new Error(`HTTP ${res.status}: non-JSON response`); }
|
|
104
119
|
}
|
|
@@ -111,7 +126,7 @@ function createGuard(opts) {
|
|
|
111
126
|
const init = { method, headers: { ...headers, ...(body ? { "content-type": "application/json" } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}) };
|
|
112
127
|
const res = await fetchBounded(url, init);
|
|
113
128
|
if (res.status !== 402) {
|
|
114
|
-
if (!res.ok) throw
|
|
129
|
+
if (!res.ok) throw httpError(res, "", false); // nothing was signed, so nothing was charged
|
|
115
130
|
return parseJson(res);
|
|
116
131
|
}
|
|
117
132
|
let challenge;
|
|
@@ -119,7 +134,7 @@ function createGuard(opts) {
|
|
|
119
134
|
const paymentRequired = httpClient.getPaymentRequiredResponse((n) => res.headers.get(n), challenge);
|
|
120
135
|
const payload = await httpClient.createPaymentPayload(paymentRequired); // policy runs (and reserves budget) inside
|
|
121
136
|
const paid = await fetchBounded(url, { ...init, headers: { ...init.headers, ...httpClient.encodePaymentSignatureHeader(payload) } });
|
|
122
|
-
if (!paid.ok) throw
|
|
137
|
+
if (!paid.ok) throw httpError(paid, "Payment failed — ");
|
|
123
138
|
let settle = null;
|
|
124
139
|
try { settle = httpClient.getPaymentSettleResponse((n) => paid.headers.get(n)) || null; } catch { settle = null; }
|
|
125
140
|
const contentType = paid.headers.get("content-type") || "";
|
|
@@ -134,6 +149,7 @@ function createGuard(opts) {
|
|
|
134
149
|
policy,
|
|
135
150
|
fetchBounded,
|
|
136
151
|
callPaid,
|
|
152
|
+
httpError,
|
|
137
153
|
limits: () => ({ maxPriceUsd, sessionBudgetUsd, spentUsd, origin, payTo: [...payTo] }),
|
|
138
154
|
};
|
|
139
155
|
}
|