netintel-mcp 1.1.53 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Karim Gueye (NetIntel)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,6 +4,17 @@ MCP server for NetIntel — 122 network intelligence tools for AI agents.
4
4
  DNS, SSL, WHOIS, email security, cloud fingerprinting, OSINT and more.
5
5
  Pay-per-call via x402 — the NetIntel API accepts USDC on Base or Solana; this MCP server pays on Base. No API keys needed — just a wallet with USDC.
6
6
 
7
+ ## Install as a Claude Code plugin (recommended)
8
+
9
+ The plugin installs the MCP server **and** a skill that knows which NetIntel endpoint fits a task and what it costs, and prompts once for the agent wallet key (stored in Claude Code's secure storage, never in settings files):
10
+
11
+ ```
12
+ /plugin marketplace add kjgueye/netintel-mcp
13
+ /plugin install netintel@netintel
14
+ ```
15
+
16
+ Then just ask — "is this IP malicious?", "does this domain have DMARC?", "fetch this URL as JSON" — and the tools pay per call from that wallet.
17
+
7
18
  ## Install
8
19
 
9
20
  ```bash
@@ -26,8 +37,8 @@ Or add manually to your Claude Desktop config:
26
37
  ```
27
38
 
28
39
  ## Requirements
29
- - A wallet private key with USDC on Base mainnet
30
- - EVM_PRIVATE_KEY environment variable set
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. **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.
31
42
 
32
43
  ## Tools
33
44
 
package/dist/client.js CHANGED
@@ -1,18 +1,111 @@
1
1
  import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
2
2
  import { registerExactEvmScheme } from "@x402/evm/exact/client";
3
3
  import { privateKeyToAccount } from "viem/accounts";
4
+ import { createPublicClient, http, formatUnits } from "viem";
5
+ import { base } from "viem/chains";
4
6
  import axios from "axios";
5
7
  // Canonical domain. The legacy netintel-production-440c.up.railway.app host
6
8
  // still serves the same app as a fallback, but new clients target netintel.dev.
7
9
  const BASE_URL = "https://netintel.dev";
10
+ /** USDC on Base mainnet — the asset every NetIntel price is quoted in. */
11
+ const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
12
+ /** Tools that work with NO wallet: NetIntel's free tier (per-client quota, then the normal price). */
13
+ export const FREE_TOOLS = [
14
+ "netintel_dns_lookup",
15
+ "netintel_ssl_cert",
16
+ "netintel_whois_lookup",
17
+ "netintel_subnet_calc",
18
+ "netintel_email_auth",
19
+ ];
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
+ }
55
+ /** The configured agent wallet's address, or null when the server runs wallet-less. */
56
+ export function configuredWallet() {
57
+ return walletAddress;
58
+ }
59
+ /**
60
+ * The HTTP client every tool uses. With EVM_PRIVATE_KEY set, responses that
61
+ * demand payment are paid automatically (x402) and retried; without it, the
62
+ * server still starts — the free-tier tools work as-is and paid tools return
63
+ * an explanation of what a call costs and how to fund a wallet, instead of
64
+ * failing to boot for everyone who installed the plugin before funding one.
65
+ */
8
66
  export async function createClient() {
9
- const key = process.env.EVM_PRIVATE_KEY;
67
+ const key = process.env.EVM_PRIVATE_KEY?.trim();
68
+ const plain = axios.create({ baseURL: BASE_URL });
10
69
  if (!key) {
11
- throw new Error("EVM_PRIVATE_KEY environment variable is required. " +
12
- "Set it to your wallet private key (with USDC on Base mainnet).");
70
+ console.error("[netintel-mcp] No EVM_PRIVATE_KEY set — running wallet-less. Free-tier tools work " +
71
+ `(${FREE_TOOLS.join(", ")}); paid tools will explain how to fund an agent wallet.`);
72
+ return plain;
13
73
  }
14
- const client = new x402Client();
15
74
  const signer = privateKeyToAccount(key);
75
+ walletAddress = signer.address;
76
+ const client = new x402Client();
16
77
  registerExactEvmScheme(client, { signer });
17
- return wrapAxiosWithPayment(axios.create({ baseURL: BASE_URL }), client);
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
+ });
97
+ return wrapAxiosWithPayment(plain, client);
98
+ }
99
+ /** USDC balance of the configured wallet on Base (keyless public RPC). */
100
+ export async function walletUsdcBalance() {
101
+ if (!walletAddress)
102
+ return null;
103
+ const pc = createPublicClient({ chain: base, transport: http() });
104
+ const raw = await pc.readContract({
105
+ address: USDC_BASE,
106
+ abi: [{ type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ name: "a", type: "address" }], outputs: [{ type: "uint256" }] }],
107
+ functionName: "balanceOf",
108
+ args: [walletAddress],
109
+ });
110
+ return formatUnits(raw, 6);
18
111
  }
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 } 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,22 @@ 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
+ }
19
+ if (status === 402) {
20
+ // The one error a user can act on: the call needs a payment that did not
21
+ // happen. Say what it costs and exactly what to do, instead of "402".
22
+ const price = ax.response?.data?.accepts?.[0]?.price;
23
+ const cost = price ? `This tool costs ${price} per call (USDC on Base). ` : "This tool is paid per call (USDC on Base). ";
24
+ const fix = configuredWallet()
25
+ ? `The agent wallet ${configuredWallet()} is configured but the payment did not settle — usually an empty wallet. Run netintel_wallet_status to check its USDC balance, then fund it on Base.`
26
+ : `No agent wallet is configured, so it cannot be paid. Set EVM_PRIVATE_KEY to a wallet holding USDC on Base (or enter it in the Claude Code plugin config), then retry. Without a wallet these tools still work free, up to a quota: ${FREE_TOOLS.join(", ")}.`;
27
+ return { content: [{ type: "text", text: `Payment required. ${cost}${fix}` }], isError: true };
28
+ }
13
29
  return {
14
30
  content: [{ type: "text", text: `Error (${status}): ${msg}` }],
15
31
  isError: true,
@@ -1262,6 +1278,27 @@ function registerTools(server, api) {
1262
1278
  }
1263
1279
  });
1264
1280
  }
1281
+ // Wallet status — free, local: lets an agent (or a human) tell "no wallet",
1282
+ // "empty wallet" and "funded" apart before spending a call on finding out.
1283
+ // Deliberately OUTSIDE registerTools(): scripts/sync-ecosystem.ts pairs each
1284
+ // server.tool() in there with the api.get/post path that follows it, and this
1285
+ // tool makes no API call.
1286
+ function registerWalletTool(server) {
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 () => {
1288
+ const address = configuredWallet();
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." };
1290
+ if (!address) {
1291
+ return ok({ wallet_configured: false, how_to_fund: "Set EVM_PRIVATE_KEY (or the plugin's wallet config) to a dedicated agent wallet, then send it a few dollars of USDC on Base mainnet — no ETH needed, settlement is gasless.", ...free });
1292
+ }
1293
+ try {
1294
+ const usdc = await walletUsdcBalance();
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 });
1296
+ }
1297
+ catch (e) {
1298
+ return ok({ wallet_configured: true, address, usdc_balance_base: null, balance_error: String(e.message).slice(0, 160), spending_limits: spendStatus(), ...free });
1299
+ }
1300
+ });
1301
+ }
1265
1302
  async function main() {
1266
1303
  const api = await createClient();
1267
1304
  const server = new McpServer({
@@ -1269,6 +1306,7 @@ async function main() {
1269
1306
  version: "1.1.0",
1270
1307
  });
1271
1308
  registerTools(server, api);
1309
+ registerWalletTool(server);
1272
1310
  const transport = new StdioServerTransport();
1273
1311
  await server.connect(transport);
1274
1312
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "netintel-mcp",
3
- "version": "1.1.53",
3
+ "version": "1.1.56",
4
4
  "mcpName": "io.github.kjgueye/netintel-mcp",
5
5
  "repository": {
6
6
  "type": "git",