netintel-mcp 1.1.54 → 1.1.56
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/dist/client.js +53 -0
- package/dist/index.js +10 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Or add manually to your Claude Desktop config:
|
|
|
38
38
|
|
|
39
39
|
## Requirements
|
|
40
40
|
- **Optional:** `EVM_PRIVATE_KEY` — a dedicated agent wallet holding USDC on Base mainnet (gasless; a few dollars is plenty). With it, every tool pays its listed price automatically.
|
|
41
|
-
- **Without a wallet** the server still runs: `netintel_dns_lookup`, `netintel_ssl_cert`, `netintel_whois_lookup`, `netintel_subnet_calc` and `netintel_email_auth` work free (about 30 calls/hour per client, then the normal price), and paid tools reply with what they cost and how to fund. `netintel_wallet_status` shows the wallet, its USDC balance and the free tools.
|
|
41
|
+
- **Without a wallet** the server still runs: `netintel_dns_lookup`, `netintel_ssl_cert`, `netintel_whois_lookup`, `netintel_subnet_calc` and `netintel_email_auth` work free (about 30 calls/hour per client, then the normal price), and paid tools reply with what they cost and how to fund. `netintel_wallet_status` shows the wallet, its USDC balance and the free tools. **Spending limits (≥1.1.55):** the server refuses to sign a payment above `NETINTEL_MAX_PER_CALL_USD` (default $0.25) or once it has authorized `NETINTEL_MAX_SESSION_USD` (default $5) in total per process; a refusal returns `SPEND_LIMIT: …` and nothing is paid. Listed prices are what a call costs, not a cap — with a wallet configured every 402 (including a free-tier route past its quota) is paid automatically, so authorize paid calls with your user first.
|
|
42
42
|
|
|
43
43
|
## Tools
|
|
44
44
|
|
package/dist/client.js
CHANGED
|
@@ -18,6 +18,40 @@ export const FREE_TOOLS = [
|
|
|
18
18
|
"netintel_email_auth",
|
|
19
19
|
];
|
|
20
20
|
let walletAddress = null;
|
|
21
|
+
/**
|
|
22
|
+
* Spending limits, enforced INSIDE the x402 client immediately before a payment
|
|
23
|
+
* payload is signed — the only place a limit can be real, because with a wallet
|
|
24
|
+
* configured every 402 (including a free-tier route past its quota) is paid
|
|
25
|
+
* automatically. The listed per-call prices are what a route costs, not a cap.
|
|
26
|
+
* NETINTEL_MAX_PER_CALL_USD — refuse any single payment above this (default $0.25)
|
|
27
|
+
* NETINTEL_MAX_SESSION_USD — refuse once this MCP server process has authorized
|
|
28
|
+
* this much in total (default $5.00)
|
|
29
|
+
* A refusal aborts the payment (nothing is signed or sent) and the tool returns
|
|
30
|
+
* "SPEND_LIMIT: …" telling the agent which limit and how to raise it. Session
|
|
31
|
+
* spend counts every authorized payment, whether or not the call then succeeds
|
|
32
|
+
* (conservative: a payment that settles for a failed call is not charged by
|
|
33
|
+
* NetIntel, but the signature was still handed out).
|
|
34
|
+
*/
|
|
35
|
+
const SPEND_LIMIT = "SPEND_LIMIT";
|
|
36
|
+
function limit(envName, fallback) {
|
|
37
|
+
const n = Number(process.env[envName]);
|
|
38
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
39
|
+
}
|
|
40
|
+
export const MAX_PER_CALL_USD = limit("NETINTEL_MAX_PER_CALL_USD", 0.25);
|
|
41
|
+
export const MAX_SESSION_USD = limit("NETINTEL_MAX_SESSION_USD", 5);
|
|
42
|
+
let sessionAuthorizedUsd = 0;
|
|
43
|
+
let refusedPayments = 0;
|
|
44
|
+
/** What the limits are and how much this server process has authorized so far. */
|
|
45
|
+
export function spendStatus() {
|
|
46
|
+
return {
|
|
47
|
+
max_per_call_usd: MAX_PER_CALL_USD,
|
|
48
|
+
max_session_usd: MAX_SESSION_USD,
|
|
49
|
+
session_authorized_usd: Number(sessionAuthorizedUsd.toFixed(6)),
|
|
50
|
+
session_remaining_usd: Number(Math.max(0, MAX_SESSION_USD - sessionAuthorizedUsd).toFixed(6)),
|
|
51
|
+
refused_payments: refusedPayments,
|
|
52
|
+
configure: "NETINTEL_MAX_PER_CALL_USD / NETINTEL_MAX_SESSION_USD (env, or the Claude Code plugin config); limits are per MCP server process",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
21
55
|
/** The configured agent wallet's address, or null when the server runs wallet-less. */
|
|
22
56
|
export function configuredWallet() {
|
|
23
57
|
return walletAddress;
|
|
@@ -41,6 +75,25 @@ export async function createClient() {
|
|
|
41
75
|
walletAddress = signer.address;
|
|
42
76
|
const client = new x402Client();
|
|
43
77
|
registerExactEvmScheme(client, { signer });
|
|
78
|
+
client.onBeforePaymentCreation(async (context) => {
|
|
79
|
+
const req = context?.selectedRequirements;
|
|
80
|
+
const atomic = Number(req?.amount);
|
|
81
|
+
const usd = atomic / 1_000_000;
|
|
82
|
+
if (!Number.isFinite(usd) || usd < 0) {
|
|
83
|
+
refusedPayments += 1;
|
|
84
|
+
return { abort: true, reason: `${SPEND_LIMIT}: the payment requirements carry no usable amount (got ${JSON.stringify(req?.amount)}); refusing to sign` };
|
|
85
|
+
}
|
|
86
|
+
if (usd > MAX_PER_CALL_USD) {
|
|
87
|
+
refusedPayments += 1;
|
|
88
|
+
return { abort: true, reason: `${SPEND_LIMIT}: this call costs ${usd} — above the per-call limit of ${MAX_PER_CALL_USD}. Ask the user; to allow it, raise NETINTEL_MAX_PER_CALL_USD (env or plugin config) and restart the server` };
|
|
89
|
+
}
|
|
90
|
+
if (sessionAuthorizedUsd + usd > MAX_SESSION_USD) {
|
|
91
|
+
refusedPayments += 1;
|
|
92
|
+
return { abort: true, reason: `${SPEND_LIMIT}: this call costs ${usd} and this session has already authorized ${sessionAuthorizedUsd.toFixed(3)} of its ${MAX_SESSION_USD} limit. Ask the user; to continue, raise NETINTEL_MAX_SESSION_USD (env or plugin config) and restart the server` };
|
|
93
|
+
}
|
|
94
|
+
sessionAuthorizedUsd += usd;
|
|
95
|
+
return undefined;
|
|
96
|
+
});
|
|
44
97
|
return wrapAxiosWithPayment(plain, client);
|
|
45
98
|
}
|
|
46
99
|
/** USDC balance of the configured wallet on Base (keyless public RPC). */
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { createClient, configuredWallet, walletUsdcBalance, FREE_TOOLS } from "./client.js";
|
|
5
|
+
import { createClient, configuredWallet, walletUsdcBalance, spendStatus, FREE_TOOLS } from "./client.js";
|
|
6
6
|
function ok(data) {
|
|
7
7
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
8
8
|
}
|
|
@@ -10,6 +10,12 @@ function err(e) {
|
|
|
10
10
|
const ax = e;
|
|
11
11
|
const status = ax.response?.status ?? "unknown";
|
|
12
12
|
const msg = ax.message ?? String(e);
|
|
13
|
+
// A spending-limit refusal: the SDK aborted BEFORE signing, so no payment was
|
|
14
|
+
// made. Hand the agent the exact reason (which limit, how to raise it).
|
|
15
|
+
const limitAt = msg.indexOf("SPEND_LIMIT:");
|
|
16
|
+
if (limitAt >= 0) {
|
|
17
|
+
return { content: [{ type: "text", text: `Payment refused by your spending limit — nothing was paid. ${msg.slice(limitAt)}` }], isError: true };
|
|
18
|
+
}
|
|
13
19
|
if (status === 402) {
|
|
14
20
|
// The one error a user can act on: the call needs a payment that did not
|
|
15
21
|
// happen. Say what it costs and exactly what to do, instead of "402".
|
|
@@ -1278,7 +1284,7 @@ function registerTools(server, api) {
|
|
|
1278
1284
|
// server.tool() in there with the api.get/post path that follows it, and this
|
|
1279
1285
|
// tool makes no API call.
|
|
1280
1286
|
function registerWalletTool(server) {
|
|
1281
|
-
server.tool("netintel_wallet_status", "Check the agent wallet this MCP server pays NetIntel with: whether a key is configured, its address,
|
|
1287
|
+
server.tool("netintel_wallet_status", "Check the agent wallet this MCP server pays NetIntel with: whether a key is configured, its address, its USDC balance on Base, and the spending limits (per call and per session) with how much this session has authorized. Free and local (no NetIntel call). Use when a paid tool reports 'Payment required' or 'SPEND_LIMIT', before funding, or to confirm a top-up arrived. Also lists the tools that work with no wallet at all.", {}, async () => {
|
|
1282
1288
|
const address = configuredWallet();
|
|
1283
1289
|
const free = { free_without_wallet: [...FREE_TOOLS], note: "Free tools are rate-limited per client (about 30/hour); over the quota they cost their normal price." };
|
|
1284
1290
|
if (!address) {
|
|
@@ -1286,10 +1292,10 @@ function registerWalletTool(server) {
|
|
|
1286
1292
|
}
|
|
1287
1293
|
try {
|
|
1288
1294
|
const usdc = await walletUsdcBalance();
|
|
1289
|
-
return ok({ wallet_configured: true, address, usdc_balance_base: usdc, funded: Number(usdc) > 0, top_up: Number(usdc) > 0 ? undefined : `Send USDC on Base to ${address}`, ...free });
|
|
1295
|
+
return ok({ wallet_configured: true, address, usdc_balance_base: usdc, funded: Number(usdc) > 0, top_up: Number(usdc) > 0 ? undefined : `Send USDC on Base to ${address}`, spending_limits: spendStatus(), ...free });
|
|
1290
1296
|
}
|
|
1291
1297
|
catch (e) {
|
|
1292
|
-
return ok({ wallet_configured: true, address, usdc_balance_base: null, balance_error: String(e.message).slice(0, 160), ...free });
|
|
1298
|
+
return ok({ wallet_configured: true, address, usdc_balance_base: null, balance_error: String(e.message).slice(0, 160), spending_limits: spendStatus(), ...free });
|
|
1293
1299
|
}
|
|
1294
1300
|
});
|
|
1295
1301
|
}
|