agent402-mcp 0.1.1 → 0.2.0

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 +6 -1
  2. package/index.js +43 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,8 +47,13 @@ Claude Code: `claude mcp add agent402 -- npx -y agent402-mcp`
47
47
  | `AGENT_KEY` | _(unset)_ | Hex private key of a wallet funded with USDC on Base. Unset = proof-of-work mode. |
48
48
  | `AGENT402_URL` | `https://agent402.tools` | Target service (point at your own deployment). |
49
49
  | `AGENT402_TOOLS` | curated set | Comma-separated slugs to expose as first-class tools. |
50
+ | `AGENT402_MAX_PER_CALL` | unlimited | Refuse any single call priced above this many USD (e.g. `0.01`). |
51
+ | `AGENT402_BUDGET` | unlimited | Hard cap on total USDC spent per session (e.g. `1.00`). |
50
52
 
51
- Use a dedicated low-value wallet for `AGENT_KEY`, funded only with what you intend to spend calls cost $0.001–$0.02 each.
53
+ Spend controls are enforced **before a payment is signed**a runaway model is
54
+ refused, not billed. `payment_info` reports the caps, what's been spent, and
55
+ what remains. Use a dedicated low-value wallet for `AGENT_KEY`, funded only
56
+ with what you intend to spend — calls cost $0.001–$0.02 each.
52
57
 
53
58
  ## Test
54
59
 
package/index.js CHANGED
@@ -11,9 +11,11 @@
11
11
  // search_tools + call_tool.
12
12
  //
13
13
  // Config (env):
14
- // AGENT402_URL target service (default https://agent402.tools)
15
- // AGENT_KEY hex private key of a funded wallet (USDC on Base) — optional
16
- // AGENT402_TOOLS comma-separated slugs to expose first-class (overrides default)
14
+ // AGENT402_URL target service (default https://agent402.tools)
15
+ // AGENT_KEY hex private key of a funded wallet (USDC on Base) — optional
16
+ // AGENT402_TOOLS comma-separated slugs to expose first-class (overrides default)
17
+ // AGENT402_MAX_PER_CALL refuse any single call priced above this many USD (e.g. 0.01)
18
+ // AGENT402_BUDGET hard cap on total USDC spent this session (e.g. 1.00)
17
19
  import { createHash } from "node:crypto";
18
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
19
21
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -21,7 +23,14 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
21
23
 
22
24
  const BASE = (process.env.AGENT402_URL || "https://agent402.tools").replace(/\/$/, "");
23
25
  const AGENT_KEY = process.env.AGENT_KEY || "";
24
- const VERSION = "0.1.0";
26
+ const VERSION = "0.2.0";
27
+
28
+ // Spend controls — enforced BEFORE a payment is ever signed, so a confused or
29
+ // runaway model cannot drain the wallet. Unset = unlimited (back-compat).
30
+ const num = (v) => (v !== undefined && v !== "" && Number.isFinite(Number(v)) ? Number(v) : undefined);
31
+ const MAX_PER_CALL = num(process.env.AGENT402_MAX_PER_CALL) ?? Infinity;
32
+ const BUDGET = num(process.env.AGENT402_BUDGET) ?? Infinity;
33
+ let spentUsd = 0;
25
34
 
26
35
  const DEFAULT_CURATED = [
27
36
  // the tools agents can't replicate locally: browser, live web, PDF, shared memory
@@ -133,8 +142,24 @@ async function callEndpoint(tool, args = {}) {
133
142
 
134
143
  let res;
135
144
  if (AGENT_KEY) {
145
+ const price = parseFloat(String(tool.price).replace(/[^0-9.]/g, "")) || 0;
146
+ if (price > MAX_PER_CALL) {
147
+ return {
148
+ content: [{ type: "text", text: `Refused without paying: "${tool.slug}" costs ${tool.price}/call, above the AGENT402_MAX_PER_CALL cap of $${MAX_PER_CALL}. Raise the cap on this MCP server to allow it.` }],
149
+ isError: true,
150
+ };
151
+ }
152
+ if (spentUsd + price > BUDGET) {
153
+ return {
154
+ content: [{ type: "text", text: `Refused without paying: session budget exhausted ($${spentUsd.toFixed(4)} of $${BUDGET} spent; "${tool.slug}" costs ${tool.price}). Restart the MCP server or raise AGENT402_BUDGET.` }],
155
+ isError: true,
156
+ };
157
+ }
136
158
  const payFetch = await getPayFetch();
137
159
  res = await payFetch(url, init);
160
+ // Count spend when the server confirms settlement (payment receipt header),
161
+ // falling back to any 2xx — conservative in the buyer's favor.
162
+ if (res.headers.get("x-payment-response") || res.ok) spentUsd += price;
138
163
  } else if (tool.computePayable) {
139
164
  // No wallet: pay with compute up front — solving before the call skips the
140
165
  // 402 round-trip entirely (challenges are single-use and tool-scoped).
@@ -263,8 +288,16 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
263
288
  tools: catalog.size,
264
289
  payableWithCompute: computePayable,
265
290
  walletOnly: catalog.size - computePayable,
291
+ spendControls: AGENT_KEY
292
+ ? {
293
+ maxPerCallUsd: MAX_PER_CALL === Infinity ? "unlimited" : MAX_PER_CALL,
294
+ sessionBudgetUsd: BUDGET === Infinity ? "unlimited" : BUDGET,
295
+ spentThisSessionUsd: Number(spentUsd.toFixed(6)),
296
+ remainingUsd: BUDGET === Infinity ? "unlimited" : Number(Math.max(0, BUDGET - spentUsd).toFixed(6)),
297
+ }
298
+ : "n/a (proof-of-work mode spends CPU, not money)",
266
299
  note: AGENT_KEY
267
- ? "Every tool is available; each call is paid in USDC via x402 from the configured wallet."
300
+ ? "Every tool is available; each call is paid in USDC via x402 from the configured wallet, within the spend controls above."
268
301
  : `No AGENT_KEY configured: ${computePayable} pure-CPU tools are free via proof-of-work; the ${catalog.size - computePayable} network/browser/memory tools need a funded wallet (set AGENT_KEY).`,
269
302
  }, null, 2),
270
303
  }],
@@ -291,6 +324,10 @@ const requested = (process.env.AGENT402_TOOLS || DEFAULT_CURATED.join(","))
291
324
  .split(",").map((s) => s.trim()).filter(Boolean);
292
325
  curated = requested.map((slug) => catalog.get(slug)).filter(Boolean);
293
326
  log(`catalog: ${catalog.size} tools from ${BASE}; ${curated.length} first-class, rest via search_tools/call_tool`);
294
- log(AGENT_KEY ? "payment: USDC via x402 (wallet configured)" : "payment: proof-of-work on eligible tools (no AGENT_KEY)");
327
+ log(
328
+ AGENT_KEY
329
+ ? `payment: USDC via x402 (wallet configured; max/call ${MAX_PER_CALL === Infinity ? "unlimited" : `$${MAX_PER_CALL}`}, budget ${BUDGET === Infinity ? "unlimited" : `$${BUDGET}`})`
330
+ : "payment: proof-of-work on eligible tools (no AGENT_KEY)"
331
+ );
295
332
 
296
333
  await server.connect(new StdioServerTransport());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent402-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "author": "Mikey Petrillo (https://github.com/MikeyPetrillo)",
5
5
  "homepage": "https://agent402.tools",
6
6
  "description": "MCP server for Agent402 (agent402.tools) — 1000+ pay-per-call web tools for AI agents. Pays per call in USDC via the x402 protocol, or with compute (proof-of-work) when no wallet is configured.",