z-zero-mcp-server 1.0.3 → 1.0.5

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 CHANGED
@@ -1,53 +1,105 @@
1
- # Z-ZERO: AI Virtual Card MCP Server
1
+ # OpenClaw: Z-ZERO AI Agent MCP Server
2
2
 
3
- A Zero-Trust Payment Protocol built specifically for AI Agents utilizing the Model Context Protocol (MCP).
3
+ A Zero-Trust Payment Protocol built specifically for AI Agents using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). Give your local agents (Claude, Cursor, AntiGravity) the ability to make real-world purchases — securely, without ever seeing a real card number.
4
4
 
5
- ## The Concept (Tokenized JIT Payments)
6
- This MCP server acts as an "Invisible Bridge" for your AI Agents. Instead of giving your LLM direct access to a 16-digit credit card number (which triggers safety filters and risks prompt injection theft), the AI only handles **temporary, single-use tokens**.
5
+ ## How It Works
7
6
 
8
- The local MCP client resolves the token securely in RAM, injects the real card data directly into the payment form (via Playwright headless browser), and clicks "Pay". The virtual card and token are burned milliseconds later.
7
+ Instead of giving your AI direct access to a credit card number, OpenClaw issues **temporary, single-use JIT tokens**. The token is resolved in RAM, Playwright injects the card data directly into the payment form, and the virtual card is burned milliseconds later. Your AI never sees the PAN, CVV, or expiry.
9
8
 
10
- ## Features
11
- - **Zero PII (Blind Execution):** AI never sees card numbers.
12
- - **Exact-Match Funding:** Tokens are tied to virtual cards locked to the exact requested amount.
13
- - **Phantom Burn:** Single-use architecture. Cards self-destruct after checking out.
14
- - **Stripe/HTML Form Injection:** Automatically fills common checkout domains.
9
+ ---
15
10
 
16
- ## Documentation
17
- Check the `docs/` folder for complete system design and architecture:
18
- - [Product Plan & Core Concept](docs/ai_card_product_plan.md)
19
- - [MCP Implementation Plan](docs/implementation_plan.md)
20
- - [Mock Backend Architecture](docs/backend_architecture.md)
21
- - [Live Demo Walkthrough & Test Results](docs/walkthrough.md)
11
+ ## Quick Install (Recommended)
22
12
 
23
- ## Getting Started
24
-
25
- ### 1. Install Dependencies
26
13
  ```bash
27
- npm install
28
- npx playwright install chromium
14
+ npx z-zero-mcp-server
29
15
  ```
30
16
 
31
- ### 2. Build the Server
32
- ```bash
33
- npm run build
34
- ```
17
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
35
18
 
36
- ### 3. Usage with Claude Desktop
37
- Add this to your `mcp_config.json`:
38
19
  ```json
39
20
  {
40
21
  "mcpServers": {
41
- "ai-card-mcp": {
42
- "command": "node",
43
- "args": ["/absolute/path/to/ai-card-mcp/dist/index.js"]
22
+ "openclaw": {
23
+ "command": "npx",
24
+ "args": ["-y", "z-zero-mcp-server"],
25
+ "env": {
26
+ "Z_ZERO_API_KEY": "zk_live_your_passport_key_here"
27
+ }
44
28
  }
45
29
  }
46
30
  }
47
31
  ```
48
32
 
33
+ Get your Passport Key at: **[clawcard.store/dashboard/agents](https://www.clawcard.store/dashboard/agents)**
34
+
35
+ ---
36
+
37
+ ## Requirements
38
+
39
+ - **Node.js v18+** — [nodejs.org](https://nodejs.org)
40
+ - **Passport Key** — starts with `zk_live_`, get it from the dashboard above
41
+
42
+ ---
43
+
49
44
  ## Available MCP Tools
50
- - `list_cards`: View available virtual cards and balances.
51
- - `check_balance`: Query a specific card's real-time balance.
52
- - `request_payment_token`: Generate a JIT auth token locked to a specific amount.
53
- - `execute_payment`: Passes the token to the Playwright bridge to auto-fill the checkout URL and burn the token.
45
+
46
+ | Tool | Description |
47
+ |------|-------------|
48
+ | `list_cards` | View your virtual card aliases and balances |
49
+ | `check_balance` | Query a specific card's real-time balance |
50
+ | `get_deposit_addresses` | Get deposit addresses to top up your balance |
51
+ | `request_payment_token` | Generate a JIT auth token for a specific amount |
52
+ | `execute_payment` | Auto-fill checkout form and execute payment |
53
+ | `cancel_payment_token` | Cancel unused token, refund to wallet |
54
+ | `request_human_approval` | Pause and ask human for approval |
55
+
56
+ ---
57
+
58
+ ## REST API Reference
59
+
60
+ The Z-ZERO backend is hosted at `https://www.clawcard.store`. All endpoints require a `Bearer` token using your Passport Key.
61
+
62
+ > ⚠️ **Use the MCP tools above instead of calling REST directly.** If you must call REST, use the exact paths below.
63
+
64
+ ### `GET /api/tokens/cards`
65
+ Returns your card list, balance, and deposit addresses.
66
+ ```bash
67
+ curl -X GET "https://www.clawcard.store/api/tokens/cards" \
68
+ -H "Authorization: Bearer zk_live_your_key"
69
+ ```
70
+
71
+ **Aliases (also work):**
72
+ - `GET /api/v1/cards` ← for agents that guess REST-style paths
73
+
74
+ ### `POST /api/tokens/issue`
75
+ Issue a JIT payment token.
76
+
77
+ ### `POST /api/tokens/resolve`
78
+ Resolve a token to card data (server-side only).
79
+
80
+ ### `POST /api/tokens/burn`
81
+ Burn a used token.
82
+
83
+ ### `POST /api/tokens/cancel`
84
+ Cancel an unused token (refunds balance).
85
+
86
+ ---
87
+
88
+ ## Troubleshooting
89
+
90
+ ### "Z_ZERO_API_KEY is missing"
91
+ 1. Go to [clawcard.store/dashboard/agents](https://www.clawcard.store/dashboard/agents)
92
+ 2. Copy your Passport Key (starts with `zk_live_`)
93
+ 3. Add it to your config as `Z_ZERO_API_KEY`
94
+ 4. **Restart** Claude Desktop / Cursor
95
+
96
+ ### "Invalid API Key" (401)
97
+ - Double-check you copied the full key (e.g. `zk_live_c0g3l`)
98
+ - Make sure there are no extra spaces or line breaks
99
+
100
+ ### "404 Not Found" on `/api/v1/cards`
101
+ - This is a legacy path alias — it should now work. If not, use `/api/tokens/cards` directly.
102
+
103
+ ---
104
+
105
+ *Security: OpenClaw never stores your Passport Key. It is passed via environment variables and card data exists only in volatile RAM during execution.*
@@ -1,22 +1,9 @@
1
1
  import type { CardData } from "./types.js";
2
- export declare function listCardsRemote(): Promise<Array<{
3
- alias: string;
4
- balance: number;
5
- currency: string;
6
- }>>;
7
- export declare function getBalanceRemote(cardAlias: string): Promise<{
8
- balance: number;
9
- currency: string;
10
- } | null>;
11
- export declare function getDepositAddressesRemote(): Promise<{
12
- evm: string;
13
- tron: string;
14
- } | null>;
2
+ export declare function listCardsRemote(): Promise<any>;
3
+ export declare function getBalanceRemote(cardAlias: string): Promise<any>;
4
+ export declare function getDepositAddressesRemote(): Promise<any>;
15
5
  export declare function issueTokenRemote(cardAlias: string, amount: number, merchant: string): Promise<any | null>;
16
6
  export declare function resolveTokenRemote(token: string): Promise<CardData | null>;
17
7
  export declare function burnTokenRemote(token: string, receipt_id?: string): Promise<boolean>;
18
- export declare function cancelTokenRemote(token: string): Promise<{
19
- success: boolean;
20
- refunded_amount: number;
21
- }>;
8
+ export declare function cancelTokenRemote(token: string): Promise<any>;
22
9
  export declare function refundUnderspendRemote(token: string, actualSpent: number): Promise<void>;
@@ -11,12 +11,17 @@ exports.resolveTokenRemote = resolveTokenRemote;
11
11
  exports.burnTokenRemote = burnTokenRemote;
12
12
  exports.cancelTokenRemote = cancelTokenRemote;
13
13
  exports.refundUnderspendRemote = refundUnderspendRemote;
14
- const API_BASE_URL = process.env.Z_ZERO_API_BASE_URL || "https://clawcard.store";
14
+ const API_BASE_URL = process.env.Z_ZERO_API_BASE_URL || "https://www.clawcard.store";
15
15
  const PASSPORT_KEY = process.env.Z_ZERO_API_KEY || "";
16
16
  if (!PASSPORT_KEY) {
17
- console.error("❌ Missing Z_ZERO_API_KEY (Your Passport) in environment variables.");
17
+ console.error("❌ ERROR: Z_ZERO_API_KEY (Passport Key) is missing!");
18
+ console.error("🔐 Please get your Passport Key from: https://www.clawcard.store/dashboard/agents");
19
+ console.error("🛠️ Setup: Ensure 'Z_ZERO_API_KEY' is set in your environment variables.");
18
20
  }
19
21
  async function apiRequest(endpoint, method = 'GET', body = null) {
22
+ if (!PASSPORT_KEY) {
23
+ return { error: "AUTH_REQUIRED", message: "Z_ZERO_API_KEY is missing. Human needs to set it in MCP config." };
24
+ }
20
25
  const url = `${API_BASE_URL.replace(/\/$/, '')}${endpoint}`;
21
26
  try {
22
27
  const res = await fetch(url, {
@@ -30,29 +35,30 @@ async function apiRequest(endpoint, method = 'GET', body = null) {
30
35
  if (!res.ok) {
31
36
  const err = await res.json().catch(() => ({ error: res.statusText }));
32
37
  console.error(`[API ERROR] ${endpoint}:`, err.error);
33
- return null;
38
+ return { error: "API_ERROR", message: err.error || res.statusText };
34
39
  }
35
40
  return await res.json();
36
41
  }
37
42
  catch (err) {
38
43
  console.error(`[NETWORK ERROR] ${endpoint}:`, err.message);
39
- return null;
44
+ return { error: "NETWORK_ERROR", message: err.message };
40
45
  }
41
46
  }
42
47
  async function listCardsRemote() {
43
- const data = await apiRequest('/api/tokens/cards', 'GET');
44
- return data?.cards || [];
48
+ return await apiRequest('/api/tokens/cards', 'GET');
45
49
  }
46
50
  async function getBalanceRemote(cardAlias) {
47
- const cards = await listCardsRemote();
48
- const card = cards.find(c => c.alias === cardAlias);
51
+ const data = await listCardsRemote();
52
+ if (data?.error)
53
+ return data;
54
+ const cards = data?.cards || [];
55
+ const card = cards.find((c) => c.alias === cardAlias);
49
56
  if (!card)
50
57
  return null;
51
58
  return { balance: card.balance, currency: card.currency };
52
59
  }
53
60
  async function getDepositAddressesRemote() {
54
- const data = await apiRequest('/api/tokens/cards', 'GET');
55
- return data?.deposit_addresses || null;
61
+ return await apiRequest('/api/tokens/cards', 'GET');
56
62
  }
57
63
  async function issueTokenRemote(cardAlias, amount, merchant) {
58
64
  const data = await apiRequest('/api/tokens/issue', 'POST', {
@@ -98,6 +104,8 @@ async function burnTokenRemote(token, receipt_id) {
98
104
  }
99
105
  async function cancelTokenRemote(token) {
100
106
  const data = await apiRequest('/api/tokens/cancel', 'POST', { token });
107
+ if (data?.error)
108
+ return data;
101
109
  return {
102
110
  success: !!data,
103
111
  refunded_amount: data?.refunded_amount || 0
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- // Z-ZERO MCP Server (z-zero-mcp-server) v1.0.2
3
+ // OpenClaw MCP Server (z-zero-mcp-server) v1.0.3
4
4
  // Exposes secure JIT payment tools to AI Agents via Model Context Protocol
5
- // Now connected to REAL Airwallex Issuing API — produces real Mastercards
5
+ // Status: Connected to Z-ZERO Gateway — produces secure JIT virtual cards
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
8
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
@@ -14,13 +14,34 @@ const playwright_bridge_js_1 = require("./playwright_bridge.js");
14
14
  // ============================================================
15
15
  const server = new mcp_js_1.McpServer({
16
16
  name: "z-zero-mcp-server",
17
- version: "1.0.2",
17
+ version: "1.0.3",
18
18
  });
19
19
  // ============================================================
20
20
  // TOOL 1: List available cards (safe - no sensitive data)
21
21
  // ============================================================
22
22
  server.tool("list_cards", "List all available virtual card aliases and their balances. No sensitive data is returned.", {}, async () => {
23
- const cards = await (0, api_backend_js_1.listCardsRemote)();
23
+ const data = await (0, api_backend_js_1.listCardsRemote)();
24
+ if (data?.error === "AUTH_REQUIRED") {
25
+ return {
26
+ content: [{
27
+ type: "text",
28
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
29
+ "👉 Please GET your key here: https://www.clawcard.store/dashboard/agents\n" +
30
+ "👉 Then SET it as the 'Z_ZERO_API_KEY' environment variable in your AI tool (Claude Desktop/Cursor) and RESTART the tool."
31
+ }],
32
+ isError: true
33
+ };
34
+ }
35
+ if (data?.error) {
36
+ return {
37
+ content: [{
38
+ type: "text",
39
+ text: `❌ API ERROR: ${data.message || data.error}\n\nCould not fetch cards. Please verify your Passport Key is correct.`
40
+ }],
41
+ isError: true
42
+ };
43
+ }
44
+ const cards = data?.cards || [];
24
45
  return {
25
46
  content: [
26
47
  {
@@ -41,13 +62,24 @@ server.tool("check_balance", "Check the remaining balance of a virtual card by i
41
62
  .string()
42
63
  .describe("The alias of the card to check, e.g. 'Card_01'"),
43
64
  }, async ({ card_alias }) => {
44
- const balance = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
45
- if (!balance) {
65
+ const data = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
66
+ if (data?.error === "AUTH_REQUIRED") {
67
+ return {
68
+ content: [{
69
+ type: "text",
70
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
71
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents\n" +
72
+ "👉 Then SET it as the 'Z_ZERO_API_KEY' environment variable and RESTART."
73
+ }],
74
+ isError: true
75
+ };
76
+ }
77
+ if (!data || data.error) {
46
78
  return {
47
79
  content: [
48
80
  {
49
81
  type: "text",
50
- text: `Card "${card_alias}" not found or API key is invalid. Use list_cards to see available cards.`,
82
+ text: `Card "${card_alias}" not found or API issue. Use list_cards to see available cards.`,
51
83
  },
52
84
  ],
53
85
  isError: true,
@@ -57,7 +89,7 @@ server.tool("check_balance", "Check the remaining balance of a virtual card by i
57
89
  content: [
58
90
  {
59
91
  type: "text",
60
- text: JSON.stringify({ card_alias, ...balance }, null, 2),
92
+ text: JSON.stringify({ card_alias, ...data }, null, 2),
61
93
  },
62
94
  ],
63
95
  };
@@ -66,13 +98,25 @@ server.tool("check_balance", "Check the remaining balance of a virtual card by i
66
98
  // TOOL 2.5: Get deposit addresses (Phase 14 feature)
67
99
  // ============================================================
68
100
  server.tool("get_deposit_addresses", "Get your unique deposit addresses for EVM networks (Base, BSC, Ethereum) and Tron. Provide these to the human user when they need to add funds to their Z-ZERO balance.", {}, async () => {
69
- const addresses = await (0, api_backend_js_1.getDepositAddressesRemote)();
101
+ const data = await (0, api_backend_js_1.getDepositAddressesRemote)();
102
+ if (data?.error === "AUTH_REQUIRED") {
103
+ return {
104
+ content: [{
105
+ type: "text",
106
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
107
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents\n" +
108
+ "👉 Then SET it as the 'Z_ZERO_API_KEY' environment variable and RESTART."
109
+ }],
110
+ isError: true
111
+ };
112
+ }
113
+ const addresses = data?.deposit_addresses;
70
114
  if (!addresses) {
71
115
  return {
72
116
  content: [
73
117
  {
74
118
  type: "text",
75
- text: "Failed to retrieve deposit addresses. Please ensure your API key (passport) is valid.",
119
+ text: "Failed to retrieve deposit addresses. Please ensure your Z_ZERO_API_KEY (Passport Key) is valid. You can find it at https://clawcard.store/dashboard/agents",
76
120
  },
77
121
  ],
78
122
  isError: true,
@@ -102,9 +146,9 @@ server.tool("get_deposit_addresses", "Get your unique deposit addresses for EVM
102
146
  };
103
147
  });
104
148
  // ============================================================
105
- // TOOL 3: Request a temporary payment token (issues real JIT card)
149
+ // TOOL 3: Request a temporary payment token (issues secure JIT card)
106
150
  // ============================================================
107
- server.tool("request_payment_token", "Request a temporary payment token for a specific amount. A real single-use virtual Mastercard is issued via Airwallex. The token is valid for 30 minutes. Min: $1, Max: $100. Use this token with execute_payment to complete a purchase.", {
151
+ server.tool("request_payment_token", "Request a temporary payment token for a specific amount. A secure single-use virtual card is issued via the Z-ZERO network. The token is valid for 30 minutes. Min: $1, Max: $100. Use this token with execute_payment to complete a purchase.", {
108
152
  card_alias: zod_1.z
109
153
  .string()
110
154
  .describe("Which card to charge, e.g. 'Card_01'"),
@@ -118,15 +162,26 @@ server.tool("request_payment_token", "Request a temporary payment token for a sp
118
162
  .describe("Name or URL of the merchant/service being purchased"),
119
163
  }, async ({ card_alias, amount, merchant }) => {
120
164
  const token = await (0, api_backend_js_1.issueTokenRemote)(card_alias, amount, merchant);
121
- if (!token) {
122
- const balance = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
165
+ if (token?.error === "AUTH_REQUIRED") {
166
+ return {
167
+ content: [{
168
+ type: "text",
169
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
170
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
171
+ }],
172
+ isError: true
173
+ };
174
+ }
175
+ if (!token || token.error) {
176
+ const balanceData = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
177
+ const balance = balanceData?.balance;
123
178
  return {
124
179
  content: [
125
180
  {
126
181
  type: "text",
127
- text: balance
128
- ? `Insufficient balance. Card "${card_alias}" has $${balance.balance} but you requested $${amount}. Or amount is outside the $1-$100 limit.`
129
- : `Card "${card_alias}" not found or API key is invalid.`,
182
+ text: balance !== undefined
183
+ ? `Insufficient balance. Card "${card_alias}" has $${balance} but you requested $${amount}. Or amount is outside the $1-$100 limit.`
184
+ : `Card "${card_alias}" not found, API key is invalid, or amount limit exceeded.`,
130
185
  },
131
186
  ],
132
187
  isError: true,
@@ -142,7 +197,7 @@ server.tool("request_payment_token", "Request a temporary payment token for a sp
142
197
  amount: token.amount,
143
198
  merchant: token.merchant,
144
199
  expires_at: expiresAt,
145
- real_card_issued: !!token.airwallex_card_id,
200
+ card_issued: true,
146
201
  instructions: "Use this token with execute_payment within 30 minutes. IMPORTANT: If the actual checkout price is HIGHER than the token amount, do NOT proceed — call cancel_payment_token first and request a new token with the correct amount.",
147
202
  }, null, 2),
148
203
  },
@@ -167,7 +222,17 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
167
222
  }, async ({ token, checkout_url, actual_amount }) => {
168
223
  // Step 1: Resolve token → card data (RAM only)
169
224
  const cardData = await (0, api_backend_js_1.resolveTokenRemote)(token);
170
- if (!cardData) {
225
+ if (cardData?.error === "AUTH_REQUIRED") {
226
+ return {
227
+ content: [{
228
+ type: "text",
229
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
230
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
231
+ }],
232
+ isError: true
233
+ };
234
+ }
235
+ if (!cardData || cardData.error) {
171
236
  return {
172
237
  content: [
173
238
  {
@@ -214,7 +279,17 @@ server.tool("cancel_payment_token", "Cancel a payment token that has not been us
214
279
  .describe("Reason for cancellation, e.g. 'Price mismatch: checkout shows $20 but token is $15'"),
215
280
  }, async ({ token, reason }) => {
216
281
  const result = await (0, api_backend_js_1.cancelTokenRemote)(token);
217
- if (!result.success) {
282
+ if (result?.error === "AUTH_REQUIRED") {
283
+ return {
284
+ content: [{
285
+ type: "text",
286
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
287
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
288
+ }],
289
+ isError: true
290
+ };
291
+ }
292
+ if (!result || !result.success) {
218
293
  return {
219
294
  content: [
220
295
  {
@@ -337,8 +412,8 @@ When asked to make a purchase, execute the following steps precisely in order:
337
412
  async function main() {
338
413
  const transport = new stdio_js_1.StdioServerTransport();
339
414
  await server.connect(transport);
340
- console.error("🔐 Z-ZERO MCP Server v1.0.2 running...");
341
- console.error("Backend: Supabase + Airwallex Issuing API (Real JIT Cards)");
415
+ console.error("🔐 OpenClaw MCP Server v1.0.3 running...");
416
+ console.error("Status: Secure & Connected to Z-ZERO Gateway");
342
417
  console.error("Tools: list_cards, check_balance, request_payment_token, execute_payment, cancel_payment_token, request_human_approval");
343
418
  }
344
419
  main().catch(console.error);
package/dist/types.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface VirtualCard {
7
7
  name: string;
8
8
  balance: number;
9
9
  currency: string;
10
+ error?: string;
11
+ message?: string;
10
12
  }
11
13
  export interface PaymentToken {
12
14
  token: string;
@@ -16,6 +18,8 @@ export interface PaymentToken {
16
18
  created_at: number;
17
19
  ttl_seconds: number;
18
20
  used: boolean;
21
+ error?: string;
22
+ message?: string;
19
23
  }
20
24
  export interface CardData {
21
25
  number: string;
@@ -23,6 +27,8 @@ export interface CardData {
23
27
  exp_year: string;
24
28
  cvv: string;
25
29
  name: string;
30
+ error?: string;
31
+ message?: string;
26
32
  }
27
33
  export interface PaymentResult {
28
34
  success: boolean;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "z-zero-mcp-server",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Z-ZERO MCP Server — Secure JIT Virtual Card Payment tools for AI Agents (Claude, AutoGPT, CrewAI) — Real Airwallex Mastercards",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/index.js",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "start": "node dist/index.js",
10
- "dev": "npx tsx src/index.ts"
10
+ "dev": "npx tsx src/index.ts",
11
+ "postinstall": "echo 'IMPORTANT: Run \"npx playwright install chromium\" to enable automated checkouts.'"
11
12
  },
12
13
  "keywords": [
13
14
  "mcp",
@@ -28,14 +29,13 @@
28
29
  ],
29
30
  "dependencies": {
30
31
  "@modelcontextprotocol/sdk": "^1.12.1",
31
- "@supabase/supabase-js": "^2.49.4",
32
32
  "dotenv": "^16.5.0",
33
33
  "playwright": "^1.52.0",
34
34
  "zod": "^3.25.11"
35
35
  },
36
36
  "devDependencies": {
37
- "typescript": "^5.8.3",
37
+ "@types/node": "^22.15.21",
38
38
  "tsx": "^4.19.4",
39
- "@types/node": "^22.15.21"
39
+ "typescript": "^5.8.3"
40
40
  }
41
41
  }
@@ -1,27 +0,0 @@
1
- import type { CardData, PaymentToken } from "./types.js";
2
- interface ExtendedToken extends PaymentToken {
3
- airwallex_card_id?: string;
4
- cancelled?: boolean;
5
- }
6
- export declare function listCardsRemote(): Promise<Array<{
7
- alias: string;
8
- balance: number;
9
- currency: string;
10
- }>>;
11
- export declare function getBalanceRemote(cardAlias: string): Promise<{
12
- balance: number;
13
- currency: string;
14
- } | null>;
15
- export declare function getDepositAddressesRemote(): Promise<{
16
- evm: string;
17
- tron: string;
18
- } | null>;
19
- export declare function issueTokenRemote(cardAlias: string, amount: number, merchant: string, ttlSeconds?: number): Promise<ExtendedToken | null>;
20
- export declare function resolveTokenRemote(tokenId: string): Promise<CardData | null>;
21
- export declare function cancelTokenRemote(tokenId: string): Promise<{
22
- success: boolean;
23
- refunded_amount: number;
24
- }>;
25
- export declare function burnTokenRemote(tokenId: string): Promise<boolean>;
26
- export declare function refundUnderspendRemote(tokenId: string, actualSpent: number): Promise<void>;
27
- export {};
@@ -1,353 +0,0 @@
1
- "use strict";
2
- // Z-ZERO Supabase Backend v2
3
- // Connects to real Supabase DB + Airwallex Issuing API for real JIT Mastercards
4
- // Card data is looked up via Z_ZERO_API_KEY, cards are issued via Airwallex
5
- var __importDefault = (this && this.__importDefault) || function (mod) {
6
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
- };
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.listCardsRemote = listCardsRemote;
10
- exports.getBalanceRemote = getBalanceRemote;
11
- exports.getDepositAddressesRemote = getDepositAddressesRemote;
12
- exports.issueTokenRemote = issueTokenRemote;
13
- exports.resolveTokenRemote = resolveTokenRemote;
14
- exports.cancelTokenRemote = cancelTokenRemote;
15
- exports.burnTokenRemote = burnTokenRemote;
16
- exports.refundUnderspendRemote = refundUnderspendRemote;
17
- const supabase_js_1 = require("@supabase/supabase-js");
18
- const crypto_1 = __importDefault(require("crypto"));
19
- // ============================================================
20
- // SUPABASE CLIENT
21
- // ============================================================
22
- const SUPABASE_URL = process.env.Z_ZERO_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || "";
23
- const SUPABASE_ANON_KEY = process.env.Z_ZERO_SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
24
- const API_KEY = process.env.Z_ZERO_API_KEY || "";
25
- let supabase = null;
26
- if (SUPABASE_URL && SUPABASE_ANON_KEY) {
27
- try {
28
- supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY);
29
- }
30
- catch (err) {
31
- console.error("Failed to initialize Supabase client:", err);
32
- }
33
- }
34
- else {
35
- console.error("⚠️ Supabase backend is disabled (Missing Z_ZERO_SUPABASE_URL/ANON_KEY).");
36
- }
37
- // ============================================================
38
- // AIRWALLEX CONFIG
39
- // ============================================================
40
- const AIRWALLEX_API_KEY = process.env.Z_ZERO_AIRWALLEX_API_KEY || "";
41
- const AIRWALLEX_CLIENT_ID = process.env.Z_ZERO_AIRWALLEX_CLIENT_ID || "";
42
- const AIRWALLEX_ENV = process.env.Z_ZERO_AIRWALLEX_ENV || "demo";
43
- const AIRWALLEX_BASE = AIRWALLEX_ENV === "prod"
44
- ? "https://api.airwallex.com/api/v1"
45
- : "https://api-demo.airwallex.com/api/v1";
46
- // Cached auth token for Airwallex (avoids re-login per call)
47
- let airwallexToken = null;
48
- let airwallexTokenExpiry = 0;
49
- async function getAirwallexToken() {
50
- if (!AIRWALLEX_API_KEY || !AIRWALLEX_CLIENT_ID)
51
- return null;
52
- if (airwallexToken && Date.now() < airwallexTokenExpiry)
53
- return airwallexToken;
54
- try {
55
- const res = await fetch(`${AIRWALLEX_BASE}/authentication/login`, {
56
- method: "POST",
57
- headers: {
58
- "x-api-key": AIRWALLEX_API_KEY,
59
- "x-client-id": AIRWALLEX_CLIENT_ID,
60
- "Content-Type": "application/json",
61
- },
62
- });
63
- if (!res.ok)
64
- throw new Error(await res.text());
65
- const data = await res.json();
66
- airwallexToken = data.token;
67
- airwallexTokenExpiry = Date.now() + 25 * 60 * 1000; // 25 min cache
68
- return airwallexToken;
69
- }
70
- catch (err) {
71
- console.error("[AIRWALLEX] Auth failed:", err);
72
- return null;
73
- }
74
- }
75
- const tokenStore = new Map();
76
- // ============================================================
77
- // RESOLVE API KEY → USER INFO
78
- // ============================================================
79
- async function resolveApiKey() {
80
- if (!API_KEY)
81
- return null;
82
- const { data: card, error } = await supabase
83
- .from("cards")
84
- .select("id, alias, user_id, allocated_limit_usd, is_active")
85
- .eq("card_number_encrypted", API_KEY)
86
- .eq("is_active", true)
87
- .single();
88
- if (error || !card) {
89
- console.error("API Key not found:", error?.message);
90
- return null;
91
- }
92
- const { data: wallet } = await supabase
93
- .from("wallets")
94
- .select("balance")
95
- .eq("user_id", card.user_id)
96
- .single();
97
- return {
98
- userId: card.user_id,
99
- walletBalance: Number(wallet?.balance || 0),
100
- cardId: card.id,
101
- cardAlias: card.alias,
102
- };
103
- }
104
- // ============================================================
105
- // PUBLIC API
106
- // ============================================================
107
- async function listCardsRemote() {
108
- const info = await resolveApiKey();
109
- if (!info)
110
- return [];
111
- return [{ alias: info.cardAlias, balance: info.walletBalance, currency: "USD" }];
112
- }
113
- async function getBalanceRemote(cardAlias) {
114
- const info = await resolveApiKey();
115
- if (!info || info.cardAlias !== cardAlias)
116
- return null;
117
- return { balance: info.walletBalance, currency: "USD" };
118
- }
119
- async function getDepositAddressesRemote() {
120
- const info = await resolveApiKey();
121
- if (!info)
122
- return null;
123
- const { data: wallet, error } = await supabase
124
- .from("deposit_wallets")
125
- .select("evm_address, tron_address")
126
- .eq("user_id", info.userId)
127
- .single();
128
- if (error || !wallet) {
129
- console.error("Deposit wallets not found for user:", info.userId);
130
- return null;
131
- }
132
- return {
133
- evm: wallet.evm_address,
134
- tron: wallet.tron_address,
135
- };
136
- }
137
- async function issueTokenRemote(cardAlias, amount, merchant, ttlSeconds = 1800 // 30 minutes default
138
- ) {
139
- // 1. Validate business rules
140
- if (amount < 1) {
141
- console.error("[ISSUE] Card amount must be at least $1.00");
142
- return null;
143
- }
144
- if (amount > 100) {
145
- console.error("[ISSUE] Card amount exceeds $100 maximum limit");
146
- return null;
147
- }
148
- // 2. Resolve user from API key
149
- const info = await resolveApiKey();
150
- if (!info)
151
- return null;
152
- if (info.cardAlias !== cardAlias)
153
- return null;
154
- if (info.walletBalance < amount) {
155
- console.error(`[ISSUE] Insufficient balance: $${info.walletBalance} < $${amount}`);
156
- return null;
157
- }
158
- const tokenId = `z_jit_${crypto_1.default.randomBytes(8).toString("hex")}`;
159
- // 3. Try to create a REAL Airwallex card
160
- let airwallexCardId;
161
- const airwallexAuth = await getAirwallexToken();
162
- if (airwallexAuth) {
163
- try {
164
- const expiryTime = new Date(Date.now() + 30 * 60 * 1000).toISOString();
165
- const payload = {
166
- request_id: tokenId,
167
- issue_to: "ORGANISATION",
168
- name_on_card: "Z-ZERO AI AGENT",
169
- form_factor: "VIRTUAL",
170
- primary_currency: "USD",
171
- valid_to: expiryTime,
172
- authorization_controls: {
173
- allowed_transaction_count: "SINGLE",
174
- transaction_limits: {
175
- currency: "USD",
176
- limits: [{ amount, interval: "ALL_TIME" }],
177
- },
178
- },
179
- };
180
- const res = await fetch(`${AIRWALLEX_BASE}/issuing/cards/create`, {
181
- method: "POST",
182
- headers: {
183
- Authorization: `Bearer ${airwallexAuth}`,
184
- "Content-Type": "application/json",
185
- },
186
- body: JSON.stringify(payload),
187
- });
188
- if (res.ok) {
189
- const data = await res.json();
190
- airwallexCardId = data.card_id;
191
- console.error(`[AIRWALLEX] ✅ Real card issued: ${data.card_id} for $${amount}`);
192
- }
193
- else {
194
- const errText = await res.text();
195
- console.error("[AIRWALLEX] Card creation failed:", errText);
196
- // Fall through — will still create token but without real card ID
197
- }
198
- }
199
- catch (err) {
200
- console.error("[AIRWALLEX] Error creating card:", err);
201
- }
202
- }
203
- else {
204
- console.error("[AIRWALLEX] No auth token — running in mock mode");
205
- }
206
- const token = {
207
- token: tokenId,
208
- card_alias: cardAlias,
209
- amount,
210
- merchant,
211
- created_at: Date.now(),
212
- ttl_seconds: ttlSeconds,
213
- used: false,
214
- cancelled: false,
215
- airwallex_card_id: airwallexCardId,
216
- };
217
- tokenStore.set(tokenId, token);
218
- // 4. Pre-hold the amount in Supabase (deduct immediately, refund on cancel or underspend)
219
- const newBalance = info.walletBalance - amount;
220
- await supabase
221
- .from("wallets")
222
- .update({ balance: newBalance })
223
- .eq("user_id", info.userId);
224
- console.error(`[ISSUE] Token ${tokenId} created. Balance: $${info.walletBalance} → $${newBalance}`);
225
- return token;
226
- }
227
- async function resolveTokenRemote(tokenId) {
228
- const token = tokenStore.get(tokenId);
229
- if (!token || token.used || token.cancelled)
230
- return null;
231
- const age = (Date.now() - token.created_at) / 1000;
232
- if (age > token.ttl_seconds) {
233
- tokenStore.delete(tokenId);
234
- return null;
235
- }
236
- // Try to fetch REAL card details from Airwallex
237
- if (token.airwallex_card_id) {
238
- const airwallexAuth = await getAirwallexToken();
239
- if (airwallexAuth) {
240
- try {
241
- const res = await fetch(`${AIRWALLEX_BASE}/issuing/cards/${token.airwallex_card_id}`, {
242
- headers: { Authorization: `Bearer ${airwallexAuth}` },
243
- });
244
- if (res.ok) {
245
- const data = await res.json();
246
- console.error("[AIRWALLEX] ✅ Real card details fetched.");
247
- return {
248
- number: data.card_number || "4242424242424242",
249
- exp_month: data.expiry_month || "12",
250
- exp_year: data.expiry_year || "2030",
251
- cvv: data.cvv || "123",
252
- name: data.name_on_card || "Z-ZERO AI AGENT",
253
- };
254
- }
255
- }
256
- catch (err) {
257
- console.error("[AIRWALLEX] Failed to fetch card details:", err);
258
- }
259
- }
260
- }
261
- // Fallback to Stripe test card (demo/sandbox mode)
262
- console.error("[AIRWALLEX] ⚠️ Using test card fallback (4242...)");
263
- return {
264
- number: "4242424242424242",
265
- exp_month: "12",
266
- exp_year: "2030",
267
- cvv: "123",
268
- name: "Z-ZERO Agent",
269
- };
270
- }
271
- async function cancelTokenRemote(tokenId) {
272
- const token = tokenStore.get(tokenId);
273
- if (!token || token.used || token.cancelled) {
274
- return { success: false, refunded_amount: 0 };
275
- }
276
- // Mark token as cancelled in memory
277
- token.cancelled = true;
278
- // Cancel the Airwallex card (if one was issued)
279
- if (token.airwallex_card_id) {
280
- const airwallexAuth = await getAirwallexToken();
281
- if (airwallexAuth) {
282
- try {
283
- await fetch(`${AIRWALLEX_BASE}/issuing/cards/${token.airwallex_card_id}/deactivate`, {
284
- method: "POST",
285
- headers: { Authorization: `Bearer ${airwallexAuth}`, "Content-Type": "application/json" },
286
- body: JSON.stringify({ cancellation_reason: "CANCELLED_BY_USER" }),
287
- });
288
- console.error(`[AIRWALLEX] Card ${token.airwallex_card_id} cancelled.`);
289
- }
290
- catch (err) {
291
- console.error("[AIRWALLEX] Failed to cancel card:", err);
292
- }
293
- }
294
- }
295
- // Refund the held amount back to wallet
296
- const info = await resolveApiKey();
297
- if (info) {
298
- const currentBalanceRes = await supabase
299
- .from("wallets")
300
- .select("balance")
301
- .eq("user_id", info.userId)
302
- .single();
303
- const currentBalance = Number(currentBalanceRes.data?.balance || 0);
304
- await supabase
305
- .from("wallets")
306
- .update({ balance: currentBalance + token.amount })
307
- .eq("user_id", info.userId);
308
- console.error(`[CANCEL] Refunded $${token.amount}. New balance: $${currentBalance + token.amount}`);
309
- }
310
- setTimeout(() => tokenStore.delete(tokenId), 5000);
311
- return { success: true, refunded_amount: token.amount };
312
- }
313
- async function burnTokenRemote(tokenId) {
314
- const token = tokenStore.get(tokenId);
315
- if (!token || token.cancelled)
316
- return false;
317
- token.used = true;
318
- // Log the transaction — NOTE: wallet was already debited at issue time
319
- const info = await resolveApiKey();
320
- if (info) {
321
- await supabase.from("transactions").insert({
322
- card_id: info.cardId,
323
- amount: token.amount,
324
- merchant: token.merchant,
325
- status: "SUCCESS",
326
- });
327
- }
328
- setTimeout(() => tokenStore.delete(tokenId), 5000);
329
- return true;
330
- }
331
- // Handle underspend: called when transaction amount < token amount
332
- async function refundUnderspendRemote(tokenId, actualSpent) {
333
- const token = tokenStore.get(tokenId);
334
- if (!token)
335
- return;
336
- const overheld = token.amount - actualSpent;
337
- if (overheld <= 0)
338
- return;
339
- const info = await resolveApiKey();
340
- if (!info)
341
- return;
342
- const currentBalanceRes = await supabase
343
- .from("wallets")
344
- .select("balance")
345
- .eq("user_id", info.userId)
346
- .single();
347
- const currentBalance = Number(currentBalanceRes.data?.balance || 0);
348
- await supabase
349
- .from("wallets")
350
- .update({ balance: currentBalance + overheld })
351
- .eq("user_id", info.userId);
352
- console.error(`[REFUND] Underspend refunded: $${overheld} (Spent $${actualSpent} of $${token.amount})`);
353
- }