@nullclear/mcp 1.1.2 → 1.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 (2) hide show
  1. package/dist/index.js +360 -24
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -12,14 +12,14 @@ import {
12
12
  ReadResourceRequestSchema
13
13
  } from "@modelcontextprotocol/sdk/types.js";
14
14
  import { NullClearClient, createNullClearFetch, NullClearEscrowAbortedError } from "@nullclear/x402-arbiter";
15
- var ARBITER_URL = process.env.ARBITER_URL || "https://api.nullclear.dev";
16
- var SANDBOX_TEST_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
17
- var DEFAULT_BUYER_KEY = process.env.BUYER_PRIVATE_KEY || SANDBOX_TEST_KEY;
15
+ var ARBITER_URL = (process.env.ARBITER_URL || "https://api.nullclear.dev").replace(/\/$/, "");
16
+ var CONFIGURED_PRIVATE_KEY = process.env.NULLCLEAR_PRIVATE_KEY || process.env.BUYER_PRIVATE_KEY;
17
+ var MCP_SESSION_ID = `mcp_stdio_${process.pid}_${Math.random().toString(36).slice(2, 8)}`;
18
18
  var client = new NullClearClient({ arbiterUrl: ARBITER_URL });
19
19
  var server = new Server(
20
20
  {
21
21
  name: "nullclear-mcp-server",
22
- version: "1.1.2"
22
+ version: "1.2.0"
23
23
  },
24
24
  {
25
25
  capabilities: {
@@ -29,14 +29,82 @@ var server = new Server(
29
29
  }
30
30
  }
31
31
  );
32
+ async function callBazaarOrProxyWithFreeTrialFirst(endpointUrl, method = "GET", body) {
33
+ const headers = {
34
+ Accept: "application/json",
35
+ "x-nullclear-free-trial": "true",
36
+ "x-mcp-session": MCP_SESSION_ID
37
+ };
38
+ if (body !== void 0 && method !== "GET") {
39
+ headers["Content-Type"] = "application/json";
40
+ }
41
+ const trialRes = await fetch(endpointUrl, {
42
+ method,
43
+ headers,
44
+ body: body !== void 0 && method !== "GET" ? typeof body === "string" ? body : JSON.stringify(body) : void 0
45
+ });
46
+ if (trialRes.status === 200) {
47
+ const text = await trialRes.text();
48
+ let parsed = text;
49
+ try {
50
+ parsed = JSON.parse(text);
51
+ } catch {
52
+ }
53
+ return {
54
+ status: 200,
55
+ data: parsed,
56
+ tier: trialRes.headers.get("x-nullclear-tier") || "free-trial",
57
+ remainingFreeCalls: trialRes.headers.get("x-nullclear-remaining-free-calls"),
58
+ receipt: trialRes.headers.get("X-NullClear-Attestation") || trialRes.headers.get("X-NullClear-Receipt-Id")
59
+ };
60
+ }
61
+ if (trialRes.status === 402 && CONFIGURED_PRIVATE_KEY) {
62
+ const nullclearFetch = createNullClearFetch({
63
+ accountOrPrivateKey: CONFIGURED_PRIVATE_KEY,
64
+ chainId: 8453,
65
+ arbiterUrl: ARBITER_URL
66
+ });
67
+ const paidRes = await nullclearFetch(endpointUrl, {
68
+ method,
69
+ headers: body !== void 0 && method !== "GET" ? { "Content-Type": "application/json" } : void 0,
70
+ body: body !== void 0 && method !== "GET" ? typeof body === "string" ? body : JSON.stringify(body) : void 0
71
+ });
72
+ const text = await paidRes.text();
73
+ let parsed = text;
74
+ try {
75
+ parsed = JSON.parse(text);
76
+ } catch {
77
+ }
78
+ return {
79
+ status: paidRes.status,
80
+ data: parsed,
81
+ tier: "x402-paid",
82
+ remainingFreeCalls: "0",
83
+ receipt: paidRes.headers.get("X-NullClear-Attestation")
84
+ };
85
+ }
86
+ const errText = await trialRes.text();
87
+ let errParsed = errText;
88
+ try {
89
+ errParsed = JSON.parse(errText);
90
+ } catch {
91
+ }
92
+ return {
93
+ status: trialRes.status,
94
+ data: errParsed,
95
+ tier: "exhausted",
96
+ remainingFreeCalls: "0",
97
+ receipt: null
98
+ };
99
+ }
32
100
  server.setRequestHandler(ListToolsRequestSchema, async () => {
33
101
  return {
34
102
  tools: [
35
103
  {
36
- name: "nullclear_check_reputation",
37
- description: "Inspect an AI agent's trust score (0-100), clearing reliability, Sybil wash-trade penalty flags, and corporate KYA backing on Base",
104
+ name: "nullclear_token_security_audit",
105
+ description: "Audit any Base ERC-20 token address for honeypots, buy/sell taxes, verified bytecode, and x402 compatibility via NullClear Bazaar. Works out-of-the-box with zero configuration (10 free daily calls included).",
38
106
  annotations: {
39
- title: "Inspect AI Agent Trust & Sybil Score",
107
+ title: "Base Token Honeypot & Security Audit",
40
108
  readOnlyHint: true,
41
109
  destructiveHint: false,
42
110
  idempotentHint: true,
@@ -44,23 +112,97 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
44
112
  },
45
113
  inputSchema: {
46
114
  type: "object",
47
- required: ["agentId"],
115
+ required: ["tokenAddress"],
48
116
  properties: {
49
- agentId: {
117
+ tokenAddress: {
50
118
  type: "string",
51
- description: "The ERC-8004 agent token ID or registered identifier (e.g. '124595')"
119
+ description: "Base ERC-20 token contract address (e.g. '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' for USDC)",
120
+ default: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
121
+ }
122
+ }
123
+ }
124
+ },
125
+ {
126
+ name: "nullclear_wallet_risk_score",
127
+ description: "Check any wallet or agent address on Base for OFAC sanctions, Sybil wash-trading concentration flags, and ERC-8004 counterparty reputation. Works out-of-the-box with zero configuration (10 free daily calls included).",
128
+ annotations: {
129
+ title: "Wallet & Agent Sanctions / Sybil Risk Score",
130
+ readOnlyHint: true,
131
+ destructiveHint: false,
132
+ idempotentHint: true,
133
+ openWorldHint: true
134
+ },
135
+ inputSchema: {
136
+ type: "object",
137
+ required: ["walletAddress"],
138
+ properties: {
139
+ walletAddress: {
140
+ type: "string",
141
+ description: "0x wallet address or ERC-8004 agent ID to inspect"
52
142
  },
53
143
  chainId: {
54
144
  type: "number",
55
- description: "Base chain ID (8453 for Mainnet, 84532 for Sepolia)",
145
+ description: "Base chain ID (default 8453)",
56
146
  default: 8453
57
147
  }
58
148
  }
59
149
  }
60
150
  },
151
+ {
152
+ name: "nullclear_crypto_market_oracle",
153
+ description: "Fetch multi-exchange spread-verified crypto spot prices (ETH/USDC, BTC/USDC, SOL/USDC, BASE/USDC) with cryptographic NullClear invariant receipts. Works out-of-the-box with zero configuration (10 free daily calls included).",
154
+ annotations: {
155
+ title: "Spread-Verified Crypto Market Price Oracle",
156
+ readOnlyHint: true,
157
+ destructiveHint: false,
158
+ idempotentHint: true,
159
+ openWorldHint: true
160
+ },
161
+ inputSchema: {
162
+ type: "object",
163
+ required: ["pair"],
164
+ properties: {
165
+ pair: {
166
+ type: "string",
167
+ description: "Trading pair symbol (e.g. 'ETH/USDC', 'BTC/USDC', 'SOL/USDC')",
168
+ default: "ETH/USDC"
169
+ }
170
+ }
171
+ }
172
+ },
173
+ {
174
+ name: "nullclear_safe_fetch",
175
+ description: "Zero-config drop-in URL fetcher with automatic Canonical LLM Refusal Guard, HTTP 5xx crash guard, empty-JSON guard, and cryptographic execution receipt. Protects agents from hallucinated or broken API responses.",
176
+ annotations: {
177
+ title: "Auto-Guarded URL Fetcher (Zero Schema Required)",
178
+ readOnlyHint: true,
179
+ destructiveHint: false,
180
+ idempotentHint: true,
181
+ openWorldHint: true
182
+ },
183
+ inputSchema: {
184
+ type: "object",
185
+ required: ["url"],
186
+ properties: {
187
+ url: {
188
+ type: "string",
189
+ description: "Target HTTPS URL to fetch and verify through NullClear Auto-Guard"
190
+ },
191
+ method: {
192
+ type: "string",
193
+ enum: ["GET", "POST"],
194
+ default: "GET"
195
+ },
196
+ body: {
197
+ type: "string",
198
+ description: "Optional JSON string body for POST requests"
199
+ }
200
+ }
201
+ }
202
+ },
61
203
  {
62
204
  name: "nullclear_escrow_x402_call",
63
- description: "Execute an x402-gated HTTP API call through NullClear deferred schema escrow. The arbiter guarantees payment is only settled if upstream returns valid data matching your schema contract. If the seller hallucinates or crashes, $0 is spent. Users can test immediately against https://api.nullclear.dev/v1/sandbox/market-feed (or fail over from https://api.nullclear.dev/v1/sandbox/hallucinating-seller to https://api.nullclear.dev/v1/sandbox/market-feed) with zero wallet setup.",
205
+ description: "Execute an x402-gated HTTP API call through NullClear deferred schema escrow. The arbiter guarantees payment is only settled if upstream returns valid data matching your schema contract. Works out-of-the-box with 10 free daily trial calls before requiring NULLCLEAR_PRIVATE_KEY.",
64
206
  annotations: {
65
207
  title: "Execute Escrowed x402 Call with Invariants",
66
208
  readOnlyHint: false,
@@ -150,7 +292,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
150
292
  },
151
293
  buyerPrivateKey: {
152
294
  type: "string",
153
- description: "Optional buyer private key (defaults to BUYER_PRIVATE_KEY environment variable)"
295
+ description: "Optional buyer private key (defaults to Sponsored Free Tier, then NULLCLEAR_PRIVATE_KEY)"
296
+ }
297
+ }
298
+ }
299
+ },
300
+ {
301
+ name: "nullclear_check_reputation",
302
+ description: "Inspect an AI agent's trust score (0-100), clearing reliability, Sybil wash-trade penalty flags, and corporate KYA backing on Base",
303
+ annotations: {
304
+ title: "Inspect AI Agent Trust & Sybil Score",
305
+ readOnlyHint: true,
306
+ destructiveHint: false,
307
+ idempotentHint: true,
308
+ openWorldHint: true
309
+ },
310
+ inputSchema: {
311
+ type: "object",
312
+ required: ["agentId"],
313
+ properties: {
314
+ agentId: {
315
+ type: "string",
316
+ description: "The ERC-8004 agent token ID or registered identifier (e.g. '124595')"
317
+ },
318
+ chainId: {
319
+ type: "number",
320
+ description: "Base chain ID (8453 for Mainnet, 84532 for Sepolia)",
321
+ default: 8453
154
322
  }
155
323
  }
156
324
  }
@@ -339,17 +507,15 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
339
507
 
340
508
  NullClear is the autonomous deferred clearing and reputation protocol for AI agent commerce on Base Mainnet.
341
509
 
342
- ### Core Philosophy
343
- Never sign away funds unconditionally. Using EIP-3009 signed authorizations and deterministic JSON schema validation, NullClear holds seller payments in deferred escrow and only settles if output adheres 100% to your contract.
510
+ ### Zero-Config Free Trial
511
+ All MCP tools work out-of-the-box with **10 free daily calls**\u2014no wallet or private key needed to get started.
344
512
 
345
- ### Primary MCP Tools
513
+ ### First-Class Capability Tools
514
+ - \`nullclear_token_security_audit\`: Audit any Base token for honeypots, taxes, and bytecode verification.
515
+ - \`nullclear_wallet_risk_score\`: Check any wallet or agent for sanctions, Sybil wash-trading, and ERC-8004 reputation.
516
+ - \`nullclear_crypto_market_oracle\`: Multi-exchange spread-verified price feed (ETH, BTC, SOL).
517
+ - \`nullclear_safe_fetch\`: Drop-in URL fetcher with Canonical LLM Refusal Guard & crash protection.
346
518
  - \`nullclear_escrow_x402_call\`: Execute deferred schema escrow with numeric bounds and LLM refusal guards.
347
- - \`nullclear_check_reputation\`: Check an ERC-8004 agent's Sybil trust score (0-100) and wash-trading flags.
348
- - \`nullclear_verify_kya_mandate\`: Verify an agent's corporate entity backing and spending limits.
349
-
350
- ### Test Sandbox
351
- - Market Feed (Success): \`https://api.nullclear.dev/v1/sandbox/market-feed\` ($0.01 USDC)
352
- - Hallucinating Seller (Fails & Voids): \`https://api.nullclear.dev/v1/sandbox/hallucinating-seller\`
353
519
  `
354
520
  }
355
521
  ]
@@ -360,6 +526,157 @@ Never sign away funds unconditionally. Using EIP-3009 signed authorizations and
360
526
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
361
527
  const { name, arguments: args } = request.params;
362
528
  try {
529
+ if (name === "nullclear_token_security_audit") {
530
+ const tokenAddress = String(args?.tokenAddress || "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913");
531
+ const url = `${ARBITER_URL}/v1/bazaar/token-security?tokenAddress=${encodeURIComponent(tokenAddress)}`;
532
+ const res = await callBazaarOrProxyWithFreeTrialFirst(url, "GET");
533
+ if (res.status === 402) {
534
+ return {
535
+ content: [
536
+ {
537
+ type: "text",
538
+ text: JSON.stringify(
539
+ {
540
+ status: "free_trial_exhausted",
541
+ message: "You have used all 10 daily free trial calls. Set NULLCLEAR_PRIVATE_KEY in your MCP environment variables to continue with automatic $0.005 USDC x402 settlement.",
542
+ tokenAddress,
543
+ details: res.data
544
+ },
545
+ null,
546
+ 2
547
+ )
548
+ }
549
+ ]
550
+ };
551
+ }
552
+ return {
553
+ content: [
554
+ {
555
+ type: "text",
556
+ text: JSON.stringify(
557
+ {
558
+ status: "success",
559
+ tier: res.tier,
560
+ remainingFreeCalls: res.remainingFreeCalls,
561
+ receipt: res.receipt,
562
+ result: res.data
563
+ },
564
+ null,
565
+ 2
566
+ )
567
+ }
568
+ ]
569
+ };
570
+ }
571
+ if (name === "nullclear_wallet_risk_score") {
572
+ const walletAddress = String(args?.walletAddress || "0xB586eFcCa41dF27ad3873BEcA54da0c30bf04d5c");
573
+ const chainId = Number(args?.chainId || 8453);
574
+ const url = `${ARBITER_URL}/v1/bazaar/wallet-risk?walletAddress=${encodeURIComponent(walletAddress)}&chainId=${chainId}`;
575
+ const res = await callBazaarOrProxyWithFreeTrialFirst(url, "GET");
576
+ if (res.status === 402) {
577
+ return {
578
+ content: [
579
+ {
580
+ type: "text",
581
+ text: JSON.stringify(
582
+ {
583
+ status: "free_trial_exhausted",
584
+ message: "You have used all 10 daily free trial calls. Set NULLCLEAR_PRIVATE_KEY in your MCP environment variables to continue with automatic $0.005 USDC x402 settlement.",
585
+ walletAddress,
586
+ details: res.data
587
+ },
588
+ null,
589
+ 2
590
+ )
591
+ }
592
+ ]
593
+ };
594
+ }
595
+ return {
596
+ content: [
597
+ {
598
+ type: "text",
599
+ text: JSON.stringify(
600
+ {
601
+ status: "success",
602
+ tier: res.tier,
603
+ remainingFreeCalls: res.remainingFreeCalls,
604
+ receipt: res.receipt,
605
+ result: res.data
606
+ },
607
+ null,
608
+ 2
609
+ )
610
+ }
611
+ ]
612
+ };
613
+ }
614
+ if (name === "nullclear_crypto_market_oracle") {
615
+ const pair = String(args?.pair || "ETH/USDC");
616
+ const url = `${ARBITER_URL}/v1/bazaar/market-oracle?pair=${encodeURIComponent(pair)}`;
617
+ const res = await callBazaarOrProxyWithFreeTrialFirst(url, "GET");
618
+ if (res.status === 402) {
619
+ return {
620
+ content: [
621
+ {
622
+ type: "text",
623
+ text: JSON.stringify(
624
+ {
625
+ status: "free_trial_exhausted",
626
+ message: "You have used all 10 daily free trial calls. Set NULLCLEAR_PRIVATE_KEY in your MCP environment variables to continue with automatic $0.01 USDC x402 settlement.",
627
+ pair,
628
+ details: res.data
629
+ },
630
+ null,
631
+ 2
632
+ )
633
+ }
634
+ ]
635
+ };
636
+ }
637
+ return {
638
+ content: [
639
+ {
640
+ type: "text",
641
+ text: JSON.stringify(
642
+ {
643
+ status: "success",
644
+ tier: res.tier,
645
+ remainingFreeCalls: res.remainingFreeCalls,
646
+ receipt: res.receipt,
647
+ result: res.data
648
+ },
649
+ null,
650
+ 2
651
+ )
652
+ }
653
+ ]
654
+ };
655
+ }
656
+ if (name === "nullclear_safe_fetch") {
657
+ const targetUrl = String(args?.url || "");
658
+ const method = args?.method || "GET";
659
+ const proxyUrl = `${ARBITER_URL}/${targetUrl}`;
660
+ const res = await callBazaarOrProxyWithFreeTrialFirst(proxyUrl, method, args?.body);
661
+ return {
662
+ content: [
663
+ {
664
+ type: "text",
665
+ text: JSON.stringify(
666
+ {
667
+ status: res.status === 200 ? "verified_success" : "auto_guard_blocked",
668
+ httpStatus: res.status,
669
+ verified: res.status === 200,
670
+ receipt: res.receipt,
671
+ data: res.data
672
+ },
673
+ null,
674
+ 2
675
+ )
676
+ }
677
+ ]
678
+ };
679
+ }
363
680
  if (name === "nullclear_check_reputation") {
364
681
  const agentId = String(args?.agentId);
365
682
  const chainId = Number(args?.chainId || 8453);
@@ -398,7 +715,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
398
715
  const rejectRefusals = args?.rejectRefusals !== void 0 ? Boolean(args.rejectRefusals) : true;
399
716
  const fallbacks = args?.fallbacks || void 0;
400
717
  const spendPolicy = args?.spendPolicy || void 0;
401
- const buyerPrivateKey = args?.buyerPrivateKey || DEFAULT_BUYER_KEY;
718
+ const buyerPrivateKey = args?.buyerPrivateKey || CONFIGURED_PRIVATE_KEY;
402
719
  const nullclearFetch = createNullClearFetch({
403
720
  accountOrPrivateKey: buyerPrivateKey,
404
721
  chainId: 8453,
@@ -433,7 +750,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
433
750
  text: JSON.stringify(
434
751
  {
435
752
  status: "cleared_and_settled",
436
- message: "Response successfully validated against schema. Payment settled.",
753
+ tier: buyerPrivateKey ? "x402-paid" : "free-trial",
754
+ message: "Response successfully validated against schema. Payment cleared.",
437
755
  attestation: response.headers.get("X-NullClear-Attestation") || "verified",
438
756
  data: parsedData
439
757
  },
@@ -445,6 +763,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
445
763
  };
446
764
  } catch (err) {
447
765
  if (err instanceof NullClearEscrowAbortedError) {
766
+ if (err.stage === "FREE_TRIAL_EXHAUSTED") {
767
+ return {
768
+ content: [
769
+ {
770
+ type: "text",
771
+ text: JSON.stringify(
772
+ {
773
+ status: "free_trial_exhausted",
774
+ message: "Daily 10 free trial calls exhausted. Set NULLCLEAR_PRIVATE_KEY in your MCP configuration to continue with automatic zero-loss x402 settlement.",
775
+ stage: err.stage
776
+ },
777
+ null,
778
+ 2
779
+ )
780
+ }
781
+ ]
782
+ };
783
+ }
448
784
  return {
449
785
  content: [
450
786
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullclear/mcp",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "mcpName": "io.github.Nullclear-Protocol/nullclear",
5
5
  "description": "Model Context Protocol server for autonomous agents using NullClear",
6
6
  "license": "MIT",
@@ -39,8 +39,8 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.6.1",
42
- "@nullclear/shared": "1.1.2",
43
- "@nullclear/x402-arbiter": "1.1.2",
42
+ "@nullclear/shared": "1.2.0",
43
+ "@nullclear/x402-arbiter": "1.2.0",
44
44
  "viem": "^2.23.0",
45
45
  "zod": "^3.24.2"
46
46
  },