z-zero-mcp-server 1.0.2 → 1.0.4

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,80 @@
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 utilizing the Model Context Protocol (MCP). Use this to give your local agents (Claude, Cursor, AutoGPT) the power to make real-world purchases securely.
4
4
 
5
5
  ## The Concept (Tokenized JIT Payments)
6
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**.
7
7
 
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.
9
-
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.
15
-
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)
8
+ The local MCP client resolves the token securely in RAM, injects the real card data directly into the payment form (via Playwright), and clicks "Pay". The virtual card and token are burned milliseconds later.
22
9
 
23
10
  ## Getting Started
24
11
 
25
- ### 1. Install Dependencies
12
+ ### 1. Requirements
13
+ - **Node.js** (v18+)
14
+ - **Git**
15
+ - **Z-ZERO Passport Key** (Get yours at [clawcard.store/dashboard/agents](https://clawcard.store/dashboard/agents))
16
+
17
+ ### 2. Installation
26
18
  ```bash
19
+ git clone https://github.com/openclaw/z-zero-mcp-server.git
20
+ cd z-zero-mcp-server
27
21
  npm install
28
22
  npx playwright install chromium
29
- ```
30
-
31
- ### 2. Build the Server
32
- ```bash
33
23
  npm run build
34
24
  ```
35
25
 
36
- ### 3. Usage with Claude Desktop
37
- Add this to your `mcp_config.json`:
26
+ ### 3. Integration into AI Tools
27
+
28
+ #### Claude Desktop
29
+ Add the following to your `~/Library/Application Support/Claude/claude_desktop_config.json`:
30
+
38
31
  ```json
39
32
  {
40
33
  "mcpServers": {
41
- "ai-card-mcp": {
34
+ "openclaw": {
42
35
  "command": "node",
43
- "args": ["/absolute/path/to/ai-card-mcp/dist/index.js"]
36
+ "args": ["/ABSOLUTE/PATH/TO/z-zero-mcp-server/dist/index.js"],
37
+ "env": {
38
+ "Z_ZERO_API_KEY": "your_passport_key_here",
39
+ "Z_ZERO_API_BASE_URL": "https://clawcard.store"
40
+ }
44
41
  }
45
42
  }
46
43
  }
47
44
  ```
48
45
 
49
- ## Available MCP Tools
50
- - `list_cards`: View available virtual cards and balances.
46
+ #### Cursor / IDEs
47
+ 1. Go to **Settings** > **Cursor Settings** > **Features** > **MCP**.
48
+ 2. Click **+ Add New MCP Server**.
49
+ 3. Name: `openclaw`
50
+ 4. Type: `command`
51
+ 5. Command: `env Z_ZERO_API_KEY=your_passport_key_here node /ABSOLUTE/PATH/TO/z-zero-mcp-server/dist/index.js`
52
+
53
+ ## Available Tools
54
+ - `list_cards`: View available virtual card aliases and balances.
51
55
  - `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.
56
+ - `request_payment_token`: Generate a JIT auth token for a specific amount.
57
+ - `execute_payment`: Securely auto-fills checkout URLs and executes the payment.
58
+ - `get_deposit_addresses`: Get your unique addresses to top up your balance.
59
+
60
+ ## Troubleshooting
61
+
62
+ ### Error: "Z_ZERO_API_KEY is missing"
63
+ If your AI Agent reports this error, it means the `Z_ZERO_API_KEY` environment variable is not set correctly in your MCP configuration.
64
+ 1. Go to [clawcard.store/dashboard/agents](https://clawcard.store/dashboard/agents).
65
+ 2. Generate or Copy your **Passport Key** (starts with `zk_live_`).
66
+ 3. Ensure it is correctly pasted into your `claude_desktop_config.json` or your IDE's MCP settings.
67
+ 4. **Restart** your AI Agent (Claude Desktop or IDE) for the changes to take effect.
68
+
69
+ ---
70
+
71
+ ## Troubleshooting
72
+
73
+ ### Error: "Z_ZERO_API_KEY is missing"
74
+ This error means your AI Agent (Claude/Cursor) doesn't have your **Passport Key** yet.
75
+ 1. Go to [clawcard.store/dashboard/agents](https://clawcard.store/dashboard/agents).
76
+ 2. Copy your **Passport Key** (starts with `zk_live_`).
77
+ 3. Add it to your config (e.g., `claude_desktop_config.json`) as `Z_ZERO_API_KEY`.
78
+ 4. **Restart** your AI Agent (Claude Desktop or IDE).
79
+
80
+ *Security Note: OpenClaw never stores your Passport Key. It is passed via environment variables and card data exists only in RAM during execution.*
@@ -0,0 +1,9 @@
1
+ import type { CardData } from "./types.js";
2
+ export declare function listCardsRemote(): Promise<any>;
3
+ export declare function getBalanceRemote(cardAlias: string): Promise<any>;
4
+ export declare function getDepositAddressesRemote(): Promise<any>;
5
+ export declare function issueTokenRemote(cardAlias: string, amount: number, merchant: string): Promise<any | null>;
6
+ export declare function resolveTokenRemote(token: string): Promise<CardData | null>;
7
+ export declare function burnTokenRemote(token: string, receipt_id?: string): Promise<boolean>;
8
+ export declare function cancelTokenRemote(token: string): Promise<any>;
9
+ export declare function refundUnderspendRemote(token: string, actualSpent: number): Promise<void>;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ // Z-ZERO Unified API Backend
3
+ // Connects to the Dashboard Next.js API for all card and token operations.
4
+ // This preserves the "Issuer Abstraction Layer" - the bot never sees direct DB or Banking keys.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listCardsRemote = listCardsRemote;
7
+ exports.getBalanceRemote = getBalanceRemote;
8
+ exports.getDepositAddressesRemote = getDepositAddressesRemote;
9
+ exports.issueTokenRemote = issueTokenRemote;
10
+ exports.resolveTokenRemote = resolveTokenRemote;
11
+ exports.burnTokenRemote = burnTokenRemote;
12
+ exports.cancelTokenRemote = cancelTokenRemote;
13
+ exports.refundUnderspendRemote = refundUnderspendRemote;
14
+ const API_BASE_URL = process.env.Z_ZERO_API_BASE_URL || "https://clawcard.store";
15
+ const PASSPORT_KEY = process.env.Z_ZERO_API_KEY || "";
16
+ if (!PASSPORT_KEY) {
17
+ console.error("❌ ERROR: Z_ZERO_API_KEY (Passport Key) is missing!");
18
+ console.error("🔐 Please get your Passport Key from: https://clawcard.store/dashboard/agents");
19
+ console.error("🛠️ Setup: Ensure 'Z_ZERO_API_KEY' is set in your environment variables.");
20
+ }
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
+ }
25
+ const url = `${API_BASE_URL.replace(/\/$/, '')}${endpoint}`;
26
+ try {
27
+ const res = await fetch(url, {
28
+ method,
29
+ headers: {
30
+ "Authorization": `Bearer ${PASSPORT_KEY}`,
31
+ "Content-Type": "application/json",
32
+ },
33
+ body: body ? JSON.stringify(body) : null,
34
+ });
35
+ if (!res.ok) {
36
+ const err = await res.json().catch(() => ({ error: res.statusText }));
37
+ console.error(`[API ERROR] ${endpoint}:`, err.error);
38
+ return { error: "API_ERROR", message: err.error || res.statusText };
39
+ }
40
+ return await res.json();
41
+ }
42
+ catch (err) {
43
+ console.error(`[NETWORK ERROR] ${endpoint}:`, err.message);
44
+ return { error: "NETWORK_ERROR", message: err.message };
45
+ }
46
+ }
47
+ async function listCardsRemote() {
48
+ return await apiRequest('/api/tokens/cards', 'GET');
49
+ }
50
+ async function getBalanceRemote(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);
56
+ if (!card)
57
+ return null;
58
+ return { balance: card.balance, currency: card.currency };
59
+ }
60
+ async function getDepositAddressesRemote() {
61
+ return await apiRequest('/api/tokens/cards', 'GET');
62
+ }
63
+ async function issueTokenRemote(cardAlias, amount, merchant) {
64
+ const data = await apiRequest('/api/tokens/issue', 'POST', {
65
+ card_alias: cardAlias,
66
+ amount,
67
+ merchant,
68
+ device_fingerprint: `mcp-host-${process.platform}-${process.arch}`,
69
+ network_id: process.env.NETWORK_ID || "unknown-local-net",
70
+ session_id: `sid-${Math.random().toString(36).substring(7)}`
71
+ });
72
+ if (!data)
73
+ return null;
74
+ // Adapt Dashboard API response to MCP expected format
75
+ return {
76
+ token: data.token,
77
+ card_alias: cardAlias,
78
+ amount: amount,
79
+ merchant: merchant,
80
+ created_at: Date.now(),
81
+ ttl_seconds: 1800, // Matching Dashboard TTL
82
+ used: false
83
+ };
84
+ }
85
+ async function resolveTokenRemote(token) {
86
+ const data = await apiRequest('/api/tokens/resolve', 'POST', { token });
87
+ if (!data)
88
+ return null;
89
+ return {
90
+ number: data.number,
91
+ exp_month: data.exp?.split('/')[0] || "12",
92
+ exp_year: "20" + (data.exp?.split('/')[1] || "30"),
93
+ cvv: data.cvv || "123",
94
+ name: data.name || "Z-ZERO AI AGENT"
95
+ };
96
+ }
97
+ async function burnTokenRemote(token, receipt_id) {
98
+ const data = await apiRequest('/api/tokens/burn', 'POST', {
99
+ token,
100
+ receipt_id,
101
+ success: true
102
+ });
103
+ return !!data;
104
+ }
105
+ async function cancelTokenRemote(token) {
106
+ const data = await apiRequest('/api/tokens/cancel', 'POST', { token });
107
+ if (data?.error)
108
+ return data;
109
+ return {
110
+ success: !!data,
111
+ refunded_amount: data?.refunded_amount || 0
112
+ };
113
+ }
114
+ async function refundUnderspendRemote(token, actualSpent) {
115
+ // Underspend is a complex feature that usually happens at the bank level,
116
+ // but for JIT tokens, the burn tool in the dashboard could handle this in the future.
117
+ // Currently we burn the full authorized amount.
118
+ console.log(`[MCP] Burned token ${token} after spending $${actualSpent}`);
119
+ }
package/dist/index.js CHANGED
@@ -1,26 +1,38 @@
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");
9
9
  const zod_1 = require("zod");
10
- const supabase_backend_js_1 = require("./supabase_backend.js");
10
+ const api_backend_js_1 = require("./api_backend.js");
11
11
  const playwright_bridge_js_1 = require("./playwright_bridge.js");
12
12
  // ============================================================
13
13
  // CREATE MCP SERVER
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, supabase_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://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
+ const cards = data?.cards || [];
24
36
  return {
25
37
  content: [
26
38
  {
@@ -41,13 +53,24 @@ server.tool("check_balance", "Check the remaining balance of a virtual card by i
41
53
  .string()
42
54
  .describe("The alias of the card to check, e.g. 'Card_01'"),
43
55
  }, async ({ card_alias }) => {
44
- const balance = await (0, supabase_backend_js_1.getBalanceRemote)(card_alias);
45
- if (!balance) {
56
+ const data = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
57
+ if (data?.error === "AUTH_REQUIRED") {
58
+ return {
59
+ content: [{
60
+ type: "text",
61
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
62
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents\n" +
63
+ "👉 Then SET it as the 'Z_ZERO_API_KEY' environment variable and RESTART."
64
+ }],
65
+ isError: true
66
+ };
67
+ }
68
+ if (!data || data.error) {
46
69
  return {
47
70
  content: [
48
71
  {
49
72
  type: "text",
50
- text: `Card "${card_alias}" not found or API key is invalid. Use list_cards to see available cards.`,
73
+ text: `Card "${card_alias}" not found or API issue. Use list_cards to see available cards.`,
51
74
  },
52
75
  ],
53
76
  isError: true,
@@ -57,15 +80,66 @@ server.tool("check_balance", "Check the remaining balance of a virtual card by i
57
80
  content: [
58
81
  {
59
82
  type: "text",
60
- text: JSON.stringify({ card_alias, ...balance }, null, 2),
83
+ text: JSON.stringify({ card_alias, ...data }, null, 2),
61
84
  },
62
85
  ],
63
86
  };
64
87
  });
65
88
  // ============================================================
66
- // TOOL 3: Request a temporary payment token (issues real JIT card)
89
+ // TOOL 2.5: Get deposit addresses (Phase 14 feature)
67
90
  // ============================================================
68
- 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.", {
91
+ 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 () => {
92
+ const data = await (0, api_backend_js_1.getDepositAddressesRemote)();
93
+ if (data?.error === "AUTH_REQUIRED") {
94
+ return {
95
+ content: [{
96
+ type: "text",
97
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
98
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents\n" +
99
+ "👉 Then SET it as the 'Z_ZERO_API_KEY' environment variable and RESTART."
100
+ }],
101
+ isError: true
102
+ };
103
+ }
104
+ const addresses = data?.deposit_addresses;
105
+ if (!addresses) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ 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",
111
+ },
112
+ ],
113
+ isError: true,
114
+ };
115
+ }
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: JSON.stringify({
121
+ networks: {
122
+ evm: {
123
+ address: addresses.evm,
124
+ supported_chains: ["Base", "BNB Smart Chain (BSC)", "Ethereum"],
125
+ tokens: ["USDC", "USDT"]
126
+ },
127
+ tron: {
128
+ address: addresses.tron,
129
+ supported_chains: ["Tron (TRC-20)"],
130
+ tokens: ["USDT"]
131
+ }
132
+ },
133
+ note: "Funds sent to these addresses will be automatically credited to your Z-ZERO balance within minutes."
134
+ }, null, 2),
135
+ },
136
+ ],
137
+ };
138
+ });
139
+ // ============================================================
140
+ // TOOL 3: Request a temporary payment token (issues secure JIT card)
141
+ // ============================================================
142
+ 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.", {
69
143
  card_alias: zod_1.z
70
144
  .string()
71
145
  .describe("Which card to charge, e.g. 'Card_01'"),
@@ -78,16 +152,27 @@ server.tool("request_payment_token", "Request a temporary payment token for a sp
78
152
  .string()
79
153
  .describe("Name or URL of the merchant/service being purchased"),
80
154
  }, async ({ card_alias, amount, merchant }) => {
81
- const token = await (0, supabase_backend_js_1.issueTokenRemote)(card_alias, amount, merchant);
82
- if (!token) {
83
- const balance = await (0, supabase_backend_js_1.getBalanceRemote)(card_alias);
155
+ const token = await (0, api_backend_js_1.issueTokenRemote)(card_alias, amount, merchant);
156
+ if (token?.error === "AUTH_REQUIRED") {
157
+ return {
158
+ content: [{
159
+ type: "text",
160
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
161
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
162
+ }],
163
+ isError: true
164
+ };
165
+ }
166
+ if (!token || token.error) {
167
+ const balanceData = await (0, api_backend_js_1.getBalanceRemote)(card_alias);
168
+ const balance = balanceData?.balance;
84
169
  return {
85
170
  content: [
86
171
  {
87
172
  type: "text",
88
- text: balance
89
- ? `Insufficient balance. Card "${card_alias}" has $${balance.balance} but you requested $${amount}. Or amount is outside the $1-$100 limit.`
90
- : `Card "${card_alias}" not found or API key is invalid.`,
173
+ text: balance !== undefined
174
+ ? `Insufficient balance. Card "${card_alias}" has $${balance} but you requested $${amount}. Or amount is outside the $1-$100 limit.`
175
+ : `Card "${card_alias}" not found, API key is invalid, or amount limit exceeded.`,
91
176
  },
92
177
  ],
93
178
  isError: true,
@@ -103,7 +188,7 @@ server.tool("request_payment_token", "Request a temporary payment token for a sp
103
188
  amount: token.amount,
104
189
  merchant: token.merchant,
105
190
  expires_at: expiresAt,
106
- real_card_issued: !!token.airwallex_card_id,
191
+ card_issued: true,
107
192
  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.",
108
193
  }, null, 2),
109
194
  },
@@ -127,8 +212,18 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
127
212
  .describe("The actual final amount on the checkout page. If different from token amount, system will auto-refund the difference."),
128
213
  }, async ({ token, checkout_url, actual_amount }) => {
129
214
  // Step 1: Resolve token → card data (RAM only)
130
- const cardData = await (0, supabase_backend_js_1.resolveTokenRemote)(token);
131
- if (!cardData) {
215
+ const cardData = await (0, api_backend_js_1.resolveTokenRemote)(token);
216
+ if (cardData?.error === "AUTH_REQUIRED") {
217
+ return {
218
+ content: [{
219
+ type: "text",
220
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
221
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
222
+ }],
223
+ isError: true
224
+ };
225
+ }
226
+ if (!cardData || cardData.error) {
132
227
  return {
133
228
  content: [
134
229
  {
@@ -142,10 +237,10 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
142
237
  // Step 2: Use Playwright to inject card into checkout form
143
238
  const result = await (0, playwright_bridge_js_1.fillCheckoutForm)(checkout_url, cardData);
144
239
  // Step 3: Burn the token (wallet was pre-debited at issue time)
145
- await (0, supabase_backend_js_1.burnTokenRemote)(token);
240
+ await (0, api_backend_js_1.burnTokenRemote)(token);
146
241
  // Step 4: Refund underspend if actual amount was less than token amount
147
242
  if (actual_amount !== undefined && result.success) {
148
- await (0, supabase_backend_js_1.refundUnderspendRemote)(token, actual_amount);
243
+ await (0, api_backend_js_1.refundUnderspendRemote)(token, actual_amount);
149
244
  }
150
245
  // Step 5: Return result (NEVER includes card numbers)
151
246
  return {
@@ -174,8 +269,18 @@ server.tool("cancel_payment_token", "Cancel a payment token that has not been us
174
269
  .string()
175
270
  .describe("Reason for cancellation, e.g. 'Price mismatch: checkout shows $20 but token is $15'"),
176
271
  }, async ({ token, reason }) => {
177
- const result = await (0, supabase_backend_js_1.cancelTokenRemote)(token);
178
- if (!result.success) {
272
+ const result = await (0, api_backend_js_1.cancelTokenRemote)(token);
273
+ if (result?.error === "AUTH_REQUIRED") {
274
+ return {
275
+ content: [{
276
+ type: "text",
277
+ text: "❌ AUTHENTICATION REQUIRED: Your Z_ZERO_API_KEY (Passport Key) is missing from the MCP configuration.\n\n" +
278
+ "👉 Please GET your key here: https://clawcard.store/dashboard/agents"
279
+ }],
280
+ isError: true
281
+ };
282
+ }
283
+ if (!result || !result.success) {
179
284
  return {
180
285
  content: [
181
286
  {
@@ -244,13 +349,62 @@ server.tool("request_human_approval", "Request human approval before proceeding
244
349
  };
245
350
  });
246
351
  // ============================================================
352
+ // RESOURCE: Z-ZERO Autonomous Payment SOP
353
+ // ============================================================
354
+ server.resource("Standard Operating Procedure (SOP) for Autonomous Payments", "mcp://resources/sop", {
355
+ description: "A comprehensive guide on how AI agents should use the Z-ZERO tools to execute secure, zero-trust payments."
356
+ }, async (uri) => {
357
+ const sopContent = `
358
+ # Z-ZERO Autonomous Payment Skill SOP (v1.0.0)
359
+
360
+ This skill provides you (the AI Agent) with the capability to autonomously purchase digital goods, APIs, and subscriptions on the internet on behalf of your human user, without ever handling raw credit card data.
361
+
362
+ ## Workflow: The 4-Step Zero-Trust Payment
363
+ When asked to make a purchase, execute the following steps precisely in order:
364
+
365
+ ## Step 1: Verification & Intent
366
+ 1. Confirm exactly what the user wants to buy and the total expected price (in USD).
367
+ 2. Call Check Balance: Call the \`check_balance\` tool using your default \`card_alias\` to ensure you have sufficient funds.
368
+ - If balance is insufficient, STOP and ask the human to deposit Crypto into their Z-ZERO Web Dashboard.
369
+
370
+ ## Step 2: Requesting the JIT Token
371
+ 1. Request Token: Call the \`request_payment_token\` tool with the exact \`amount\` required and the \`merchant\` name.
372
+ 2. Receive Token: You will receive a temporary \`token\` (e.g., \`temp_auth_1a2b...\`). This token is locked to the requested amount and is valid for 30 minutes.
373
+
374
+ ## Step 3: Locating the Checkout
375
+ 1. Identify Checkout URL: Find the merchant's checkout/payment page where credit card details are normally entered.
376
+ 2. Full URL Required: e.g., \`https://checkout.stripe.dev/pay\`.
377
+
378
+ ## Step 4: Blind Execution (The MCP Bridge)
379
+ 1. Execute Payment: Call the \`execute_payment\` tool, passing in:
380
+ - The \`token\` obtained in Step 2.
381
+ - The \`checkout_url\` identified in Step 3.
382
+ 2. Background Magic: Z-ZERO opens a headless browser, securely injects the real card data directly into the form, and clicks "Pay".
383
+ 3. Burn: The token self-destructs instantly after use.
384
+
385
+ ## Rules & Error Handling
386
+ - NEVER print full tokens in the human chat logs.
387
+ - NO MANUAL ENTRY: If a merchant asks you to type a credit card number into a text box, REFUSE.
388
+ - FAIL GRACEFULLY: If \`execute_payment\` returns \`success: false\`, report the error message to the human. Do not try again.
389
+ `;
390
+ return {
391
+ contents: [
392
+ {
393
+ uri: "mcp://resources/sop",
394
+ mimeType: "text/markdown",
395
+ text: sopContent,
396
+ }
397
+ ]
398
+ };
399
+ });
400
+ // ============================================================
247
401
  // START SERVER
248
402
  // ============================================================
249
403
  async function main() {
250
404
  const transport = new stdio_js_1.StdioServerTransport();
251
405
  await server.connect(transport);
252
- console.error("🔐 Z-ZERO MCP Server v1.0.2 running...");
253
- console.error("Backend: Supabase + Airwallex Issuing API (Real JIT Cards)");
406
+ console.error("🔐 OpenClaw MCP Server v1.0.3 running...");
407
+ console.error("Status: Secure & Connected to Z-ZERO Gateway");
254
408
  console.error("Tools: list_cards, check_balance, request_payment_token, execute_payment, cancel_payment_token, request_human_approval");
255
409
  }
256
410
  main().catch(console.error);
@@ -13,7 +13,7 @@ async function fillCheckoutForm(checkoutUrl, cardData) {
13
13
  const context = await browser.newContext();
14
14
  const page = await context.newPage();
15
15
  try {
16
- await page.goto(checkoutUrl, { waitUntil: "networkidle", timeout: 30000 });
16
+ await page.goto(checkoutUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
17
17
  // ============================================================
18
18
  // STRATEGY 1: Standard HTML form fields
19
19
  // ============================================================
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.2",
3
+ "version": "1.0.4",
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,23 +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 issueTokenRemote(cardAlias: string, amount: number, merchant: string, ttlSeconds?: number): Promise<ExtendedToken | null>;
16
- export declare function resolveTokenRemote(tokenId: string): Promise<CardData | null>;
17
- export declare function cancelTokenRemote(tokenId: string): Promise<{
18
- success: boolean;
19
- refunded_amount: number;
20
- }>;
21
- export declare function burnTokenRemote(tokenId: string): Promise<boolean>;
22
- export declare function refundUnderspendRemote(tokenId: string, actualSpent: number): Promise<void>;
23
- export {};
@@ -1,326 +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.issueTokenRemote = issueTokenRemote;
12
- exports.resolveTokenRemote = resolveTokenRemote;
13
- exports.cancelTokenRemote = cancelTokenRemote;
14
- exports.burnTokenRemote = burnTokenRemote;
15
- exports.refundUnderspendRemote = refundUnderspendRemote;
16
- const supabase_js_1 = require("@supabase/supabase-js");
17
- const crypto_1 = __importDefault(require("crypto"));
18
- // ============================================================
19
- // SUPABASE CLIENT
20
- // ============================================================
21
- const SUPABASE_URL = process.env.Z_ZERO_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || "";
22
- const SUPABASE_ANON_KEY = process.env.Z_ZERO_SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
23
- const API_KEY = process.env.Z_ZERO_API_KEY || "";
24
- if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
25
- console.error("❌ Missing Z_ZERO_SUPABASE_URL or Z_ZERO_SUPABASE_ANON_KEY env vars");
26
- }
27
- const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY);
28
- // ============================================================
29
- // AIRWALLEX CONFIG
30
- // ============================================================
31
- const AIRWALLEX_API_KEY = process.env.Z_ZERO_AIRWALLEX_API_KEY || "";
32
- const AIRWALLEX_CLIENT_ID = process.env.Z_ZERO_AIRWALLEX_CLIENT_ID || "";
33
- const AIRWALLEX_ENV = process.env.Z_ZERO_AIRWALLEX_ENV || "demo";
34
- const AIRWALLEX_BASE = AIRWALLEX_ENV === "prod"
35
- ? "https://api.airwallex.com/api/v1"
36
- : "https://api-demo.airwallex.com/api/v1";
37
- // Cached auth token for Airwallex (avoids re-login per call)
38
- let airwallexToken = null;
39
- let airwallexTokenExpiry = 0;
40
- async function getAirwallexToken() {
41
- if (!AIRWALLEX_API_KEY || !AIRWALLEX_CLIENT_ID)
42
- return null;
43
- if (airwallexToken && Date.now() < airwallexTokenExpiry)
44
- return airwallexToken;
45
- try {
46
- const res = await fetch(`${AIRWALLEX_BASE}/authentication/login`, {
47
- method: "POST",
48
- headers: {
49
- "x-api-key": AIRWALLEX_API_KEY,
50
- "x-client-id": AIRWALLEX_CLIENT_ID,
51
- "Content-Type": "application/json",
52
- },
53
- });
54
- if (!res.ok)
55
- throw new Error(await res.text());
56
- const data = await res.json();
57
- airwallexToken = data.token;
58
- airwallexTokenExpiry = Date.now() + 25 * 60 * 1000; // 25 min cache
59
- return airwallexToken;
60
- }
61
- catch (err) {
62
- console.error("[AIRWALLEX] Auth failed:", err);
63
- return null;
64
- }
65
- }
66
- const tokenStore = new Map();
67
- // ============================================================
68
- // RESOLVE API KEY → USER INFO
69
- // ============================================================
70
- async function resolveApiKey() {
71
- if (!API_KEY)
72
- return null;
73
- const { data: card, error } = await supabase
74
- .from("cards")
75
- .select("id, alias, user_id, allocated_limit_usd, is_active")
76
- .eq("card_number_encrypted", API_KEY)
77
- .eq("is_active", true)
78
- .single();
79
- if (error || !card) {
80
- console.error("API Key not found:", error?.message);
81
- return null;
82
- }
83
- const { data: wallet } = await supabase
84
- .from("wallets")
85
- .select("balance")
86
- .eq("user_id", card.user_id)
87
- .single();
88
- return {
89
- userId: card.user_id,
90
- walletBalance: Number(wallet?.balance || 0),
91
- cardId: card.id,
92
- cardAlias: card.alias,
93
- };
94
- }
95
- // ============================================================
96
- // PUBLIC API
97
- // ============================================================
98
- async function listCardsRemote() {
99
- const info = await resolveApiKey();
100
- if (!info)
101
- return [];
102
- return [{ alias: info.cardAlias, balance: info.walletBalance, currency: "USD" }];
103
- }
104
- async function getBalanceRemote(cardAlias) {
105
- const info = await resolveApiKey();
106
- if (!info || info.cardAlias !== cardAlias)
107
- return null;
108
- return { balance: info.walletBalance, currency: "USD" };
109
- }
110
- async function issueTokenRemote(cardAlias, amount, merchant, ttlSeconds = 1800 // 30 minutes default
111
- ) {
112
- // 1. Validate business rules
113
- if (amount < 1) {
114
- console.error("[ISSUE] Card amount must be at least $1.00");
115
- return null;
116
- }
117
- if (amount > 100) {
118
- console.error("[ISSUE] Card amount exceeds $100 maximum limit");
119
- return null;
120
- }
121
- // 2. Resolve user from API key
122
- const info = await resolveApiKey();
123
- if (!info)
124
- return null;
125
- if (info.cardAlias !== cardAlias)
126
- return null;
127
- if (info.walletBalance < amount) {
128
- console.error(`[ISSUE] Insufficient balance: $${info.walletBalance} < $${amount}`);
129
- return null;
130
- }
131
- const tokenId = `z_jit_${crypto_1.default.randomBytes(8).toString("hex")}`;
132
- // 3. Try to create a REAL Airwallex card
133
- let airwallexCardId;
134
- const airwallexAuth = await getAirwallexToken();
135
- if (airwallexAuth) {
136
- try {
137
- const expiryTime = new Date(Date.now() + 30 * 60 * 1000).toISOString();
138
- const payload = {
139
- request_id: tokenId,
140
- issue_to: "ORGANISATION",
141
- name_on_card: "Z-ZERO AI AGENT",
142
- form_factor: "VIRTUAL",
143
- primary_currency: "USD",
144
- valid_to: expiryTime,
145
- authorization_controls: {
146
- allowed_transaction_count: "SINGLE",
147
- transaction_limits: {
148
- currency: "USD",
149
- limits: [{ amount, interval: "ALL_TIME" }],
150
- },
151
- },
152
- };
153
- const res = await fetch(`${AIRWALLEX_BASE}/issuing/cards/create`, {
154
- method: "POST",
155
- headers: {
156
- Authorization: `Bearer ${airwallexAuth}`,
157
- "Content-Type": "application/json",
158
- },
159
- body: JSON.stringify(payload),
160
- });
161
- if (res.ok) {
162
- const data = await res.json();
163
- airwallexCardId = data.card_id;
164
- console.error(`[AIRWALLEX] ✅ Real card issued: ${data.card_id} for $${amount}`);
165
- }
166
- else {
167
- const errText = await res.text();
168
- console.error("[AIRWALLEX] Card creation failed:", errText);
169
- // Fall through — will still create token but without real card ID
170
- }
171
- }
172
- catch (err) {
173
- console.error("[AIRWALLEX] Error creating card:", err);
174
- }
175
- }
176
- else {
177
- console.error("[AIRWALLEX] No auth token — running in mock mode");
178
- }
179
- const token = {
180
- token: tokenId,
181
- card_alias: cardAlias,
182
- amount,
183
- merchant,
184
- created_at: Date.now(),
185
- ttl_seconds: ttlSeconds,
186
- used: false,
187
- cancelled: false,
188
- airwallex_card_id: airwallexCardId,
189
- };
190
- tokenStore.set(tokenId, token);
191
- // 4. Pre-hold the amount in Supabase (deduct immediately, refund on cancel or underspend)
192
- const newBalance = info.walletBalance - amount;
193
- await supabase
194
- .from("wallets")
195
- .update({ balance: newBalance })
196
- .eq("user_id", info.userId);
197
- console.error(`[ISSUE] Token ${tokenId} created. Balance: $${info.walletBalance} → $${newBalance}`);
198
- return token;
199
- }
200
- async function resolveTokenRemote(tokenId) {
201
- const token = tokenStore.get(tokenId);
202
- if (!token || token.used || token.cancelled)
203
- return null;
204
- const age = (Date.now() - token.created_at) / 1000;
205
- if (age > token.ttl_seconds) {
206
- tokenStore.delete(tokenId);
207
- return null;
208
- }
209
- // Try to fetch REAL card details from Airwallex
210
- if (token.airwallex_card_id) {
211
- const airwallexAuth = await getAirwallexToken();
212
- if (airwallexAuth) {
213
- try {
214
- const res = await fetch(`${AIRWALLEX_BASE}/issuing/cards/${token.airwallex_card_id}`, {
215
- headers: { Authorization: `Bearer ${airwallexAuth}` },
216
- });
217
- if (res.ok) {
218
- const data = await res.json();
219
- console.error("[AIRWALLEX] ✅ Real card details fetched.");
220
- return {
221
- number: data.card_number || "4242424242424242",
222
- exp_month: data.expiry_month || "12",
223
- exp_year: data.expiry_year || "2030",
224
- cvv: data.cvv || "123",
225
- name: data.name_on_card || "Z-ZERO AI AGENT",
226
- };
227
- }
228
- }
229
- catch (err) {
230
- console.error("[AIRWALLEX] Failed to fetch card details:", err);
231
- }
232
- }
233
- }
234
- // Fallback to Stripe test card (demo/sandbox mode)
235
- console.error("[AIRWALLEX] ⚠️ Using test card fallback (4242...)");
236
- return {
237
- number: "4242424242424242",
238
- exp_month: "12",
239
- exp_year: "2030",
240
- cvv: "123",
241
- name: "Z-ZERO Agent",
242
- };
243
- }
244
- async function cancelTokenRemote(tokenId) {
245
- const token = tokenStore.get(tokenId);
246
- if (!token || token.used || token.cancelled) {
247
- return { success: false, refunded_amount: 0 };
248
- }
249
- // Mark token as cancelled in memory
250
- token.cancelled = true;
251
- // Cancel the Airwallex card (if one was issued)
252
- if (token.airwallex_card_id) {
253
- const airwallexAuth = await getAirwallexToken();
254
- if (airwallexAuth) {
255
- try {
256
- await fetch(`${AIRWALLEX_BASE}/issuing/cards/${token.airwallex_card_id}/deactivate`, {
257
- method: "POST",
258
- headers: { Authorization: `Bearer ${airwallexAuth}`, "Content-Type": "application/json" },
259
- body: JSON.stringify({ cancellation_reason: "CANCELLED_BY_USER" }),
260
- });
261
- console.error(`[AIRWALLEX] Card ${token.airwallex_card_id} cancelled.`);
262
- }
263
- catch (err) {
264
- console.error("[AIRWALLEX] Failed to cancel card:", err);
265
- }
266
- }
267
- }
268
- // Refund the held amount back to wallet
269
- const info = await resolveApiKey();
270
- if (info) {
271
- const currentBalanceRes = await supabase
272
- .from("wallets")
273
- .select("balance")
274
- .eq("user_id", info.userId)
275
- .single();
276
- const currentBalance = Number(currentBalanceRes.data?.balance || 0);
277
- await supabase
278
- .from("wallets")
279
- .update({ balance: currentBalance + token.amount })
280
- .eq("user_id", info.userId);
281
- console.error(`[CANCEL] Refunded $${token.amount}. New balance: $${currentBalance + token.amount}`);
282
- }
283
- setTimeout(() => tokenStore.delete(tokenId), 5000);
284
- return { success: true, refunded_amount: token.amount };
285
- }
286
- async function burnTokenRemote(tokenId) {
287
- const token = tokenStore.get(tokenId);
288
- if (!token || token.cancelled)
289
- return false;
290
- token.used = true;
291
- // Log the transaction — NOTE: wallet was already debited at issue time
292
- const info = await resolveApiKey();
293
- if (info) {
294
- await supabase.from("transactions").insert({
295
- card_id: info.cardId,
296
- amount: token.amount,
297
- merchant: token.merchant,
298
- status: "SUCCESS",
299
- });
300
- }
301
- setTimeout(() => tokenStore.delete(tokenId), 5000);
302
- return true;
303
- }
304
- // Handle underspend: called when transaction amount < token amount
305
- async function refundUnderspendRemote(tokenId, actualSpent) {
306
- const token = tokenStore.get(tokenId);
307
- if (!token)
308
- return;
309
- const overheld = token.amount - actualSpent;
310
- if (overheld <= 0)
311
- return;
312
- const info = await resolveApiKey();
313
- if (!info)
314
- return;
315
- const currentBalanceRes = await supabase
316
- .from("wallets")
317
- .select("balance")
318
- .eq("user_id", info.userId)
319
- .single();
320
- const currentBalance = Number(currentBalanceRes.data?.balance || 0);
321
- await supabase
322
- .from("wallets")
323
- .update({ balance: currentBalance + overheld })
324
- .eq("user_id", info.userId);
325
- console.error(`[REFUND] Underspend refunded: $${overheld} (Spent $${actualSpent} of $${token.amount})`);
326
- }