z-zero-mcp-server 1.0.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.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Z-ZERO: AI Virtual Card MCP Server
2
+
3
+ A Zero-Trust Payment Protocol built specifically for AI Agents utilizing the Model Context Protocol (MCP).
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**.
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)
22
+
23
+ ## Getting Started
24
+
25
+ ### 1. Install Dependencies
26
+ ```bash
27
+ npm install
28
+ npx playwright install chromium
29
+ ```
30
+
31
+ ### 2. Build the Server
32
+ ```bash
33
+ npm run build
34
+ ```
35
+
36
+ ### 3. Usage with Claude Desktop
37
+ Add this to your `mcp_config.json`:
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "ai-card-mcp": {
42
+ "command": "node",
43
+ "args": ["/absolute/path/to/ai-card-mcp/dist/index.js"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // Z-ZERO MCP Server (z-zero-mcp-server)
4
+ // Exposes secure JIT payment tools to AI Agents via Model Context Protocol
5
+ // Connected to Z-ZERO Supabase backend via Z_ZERO_API_KEY
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const zod_1 = require("zod");
10
+ const supabase_backend_js_1 = require("./supabase_backend.js");
11
+ const playwright_bridge_js_1 = require("./playwright_bridge.js");
12
+ // ============================================================
13
+ // CREATE MCP SERVER
14
+ // ============================================================
15
+ const server = new mcp_js_1.McpServer({
16
+ name: "z-zero-mcp-server",
17
+ version: "1.0.0",
18
+ });
19
+ // ============================================================
20
+ // TOOL 1: List available cards (safe - no sensitive data)
21
+ // ============================================================
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)();
24
+ return {
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text: JSON.stringify({
29
+ cards,
30
+ note: "Use card aliases to request payment tokens. Never ask for real card numbers.",
31
+ }, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ });
36
+ // ============================================================
37
+ // TOOL 2: Check card balance (safe)
38
+ // ============================================================
39
+ server.tool("check_balance", "Check the remaining balance of a virtual card by its alias.", {
40
+ card_alias: zod_1.z
41
+ .string()
42
+ .describe("The alias of the card to check, e.g. 'Card_01'"),
43
+ }, async ({ card_alias }) => {
44
+ const balance = await (0, supabase_backend_js_1.getBalanceRemote)(card_alias);
45
+ if (!balance) {
46
+ return {
47
+ content: [
48
+ {
49
+ type: "text",
50
+ text: `Card "${card_alias}" not found or API key is invalid. Use list_cards to see available cards.`,
51
+ },
52
+ ],
53
+ isError: true,
54
+ };
55
+ }
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: JSON.stringify({ card_alias, ...balance }, null, 2),
61
+ },
62
+ ],
63
+ };
64
+ });
65
+ // ============================================================
66
+ // TOOL 3: Request a temporary payment token
67
+ // ============================================================
68
+ server.tool("request_payment_token", "Request a temporary payment token for a specific amount. The token is valid for 15 minutes and locked to the specified amount. Use this token with execute_payment to complete a purchase.", {
69
+ card_alias: zod_1.z
70
+ .string()
71
+ .describe("Which card to charge, e.g. 'Card_01'"),
72
+ amount: zod_1.z
73
+ .number()
74
+ .positive()
75
+ .describe("Amount in USD to authorize"),
76
+ merchant: zod_1.z
77
+ .string()
78
+ .describe("Name or URL of the merchant/service being purchased"),
79
+ }, async ({ card_alias, amount, merchant }) => {
80
+ const token = await (0, supabase_backend_js_1.issueTokenRemote)(card_alias, amount, merchant);
81
+ if (!token) {
82
+ const balance = await (0, supabase_backend_js_1.getBalanceRemote)(card_alias);
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: balance
88
+ ? `Insufficient balance. Card "${card_alias}" has $${balance.balance} but you requested $${amount}.`
89
+ : `Card "${card_alias}" not found or API key is invalid.`,
90
+ },
91
+ ],
92
+ isError: true,
93
+ };
94
+ }
95
+ const expiresAt = new Date(token.created_at + token.ttl_seconds * 1000).toISOString();
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: JSON.stringify({
101
+ token: token.token,
102
+ amount: token.amount,
103
+ merchant: token.merchant,
104
+ expires_at: expiresAt,
105
+ instructions: "Use this token with execute_payment tool within 15 minutes.",
106
+ }, null, 2),
107
+ },
108
+ ],
109
+ };
110
+ });
111
+ // ============================================================
112
+ // TOOL 4: Execute payment (The "Invisible Hand")
113
+ // ============================================================
114
+ server.tool("execute_payment", "Execute a payment using a temporary token. This tool will securely fill the checkout form on the target website. You will NEVER see the real card number - it is handled securely in the background.", {
115
+ token: zod_1.z
116
+ .string()
117
+ .describe("The temporary payment token from request_payment_token"),
118
+ checkout_url: zod_1.z
119
+ .string()
120
+ .url()
121
+ .describe("The full URL of the checkout/payment page"),
122
+ }, async ({ token, checkout_url }) => {
123
+ // Step 1: Resolve token → card data (RAM only)
124
+ const cardData = (0, supabase_backend_js_1.resolveTokenRemote)(token);
125
+ if (!cardData) {
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: "Payment failed: Token is invalid, expired, or already used. Request a new token.",
131
+ },
132
+ ],
133
+ isError: true,
134
+ };
135
+ }
136
+ // Step 2: Use Playwright to inject card into checkout form
137
+ const result = await (0, playwright_bridge_js_1.fillCheckoutForm)(checkout_url, cardData);
138
+ // Step 3: Burn the token + deduct from Supabase wallet
139
+ await (0, supabase_backend_js_1.burnTokenRemote)(token);
140
+ // Step 4: Return result (NEVER includes card numbers)
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: JSON.stringify({
146
+ success: result.success,
147
+ message: result.message,
148
+ receipt_id: result.receipt_id || null,
149
+ token_status: "BURNED",
150
+ note: "Token has been permanently invalidated after this transaction.",
151
+ }, null, 2),
152
+ },
153
+ ],
154
+ };
155
+ });
156
+ // ============================================================
157
+ // START SERVER
158
+ // ============================================================
159
+ async function main() {
160
+ const transport = new stdio_js_1.StdioServerTransport();
161
+ await server.connect(transport);
162
+ console.error("🔐 Z-ZERO MCP Server running...");
163
+ console.error("Connected to: Supabase backend");
164
+ console.error("Tools: list_cards, check_balance, request_payment_token, execute_payment");
165
+ }
166
+ main().catch(console.error);
@@ -0,0 +1,32 @@
1
+ import type { PaymentToken, CardData } from "./types.js";
2
+ /**
3
+ * Issue a temporary payment token for a specific card and amount.
4
+ * Token is valid for `ttlSeconds` (default: 900 = 15 minutes).
5
+ */
6
+ export declare function issueToken(cardAlias: string, amount: number, merchant: string, ttlSeconds?: number): PaymentToken | null;
7
+ /**
8
+ * Resolve a token to real card data.
9
+ * CRITICAL: The returned CardData object lives ONLY in RAM.
10
+ * It must NEVER be logged, serialized, or returned to the AI.
11
+ */
12
+ export declare function resolveToken(tokenId: string): CardData | null;
13
+ /**
14
+ * Burn a token after use - makes it permanently invalid.
15
+ * Also deducts the amount from the card balance.
16
+ */
17
+ export declare function burnToken(tokenId: string): boolean;
18
+ /**
19
+ * Check balance of a card (safe to expose to AI).
20
+ */
21
+ export declare function getBalance(cardAlias: string): {
22
+ balance: number;
23
+ currency: string;
24
+ } | null;
25
+ /**
26
+ * List available card aliases (safe to expose to AI - no sensitive data).
27
+ */
28
+ export declare function listCards(): Array<{
29
+ alias: string;
30
+ balance: number;
31
+ currency: string;
32
+ }>;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ // Mock Neobank Backend - Simulates the card vault and token issuing system
3
+ // In production, this would be replaced by your real Neobank API
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.issueToken = issueToken;
9
+ exports.resolveToken = resolveToken;
10
+ exports.burnToken = burnToken;
11
+ exports.getBalance = getBalance;
12
+ exports.listCards = listCards;
13
+ const crypto_1 = __importDefault(require("crypto"));
14
+ // ============================================================
15
+ // CARD VAULT (In-memory store - simulates encrypted database)
16
+ // ============================================================
17
+ const cardVault = new Map([
18
+ [
19
+ "Card_01",
20
+ {
21
+ alias: "Card_01",
22
+ number: "4242424242424242", // Stripe test card
23
+ exp_month: "12",
24
+ exp_year: "2030",
25
+ cvv: "123",
26
+ name: "AI Agent Card 01",
27
+ balance: 50.0,
28
+ currency: "USD",
29
+ },
30
+ ],
31
+ [
32
+ "Card_02",
33
+ {
34
+ alias: "Card_02",
35
+ number: "5555555555554444", // Mastercard test
36
+ exp_month: "06",
37
+ exp_year: "2029",
38
+ cvv: "456",
39
+ name: "AI Agent Card 02",
40
+ balance: 100.0,
41
+ currency: "USD",
42
+ },
43
+ ],
44
+ ]);
45
+ // ============================================================
46
+ // TOKEN STORE (Active JIT tokens)
47
+ // ============================================================
48
+ const tokenStore = new Map();
49
+ // ============================================================
50
+ // PUBLIC API
51
+ // ============================================================
52
+ /**
53
+ * Issue a temporary payment token for a specific card and amount.
54
+ * Token is valid for `ttlSeconds` (default: 900 = 15 minutes).
55
+ */
56
+ function issueToken(cardAlias, amount, merchant, ttlSeconds = 900) {
57
+ const card = cardVault.get(cardAlias);
58
+ if (!card) {
59
+ return null;
60
+ }
61
+ if (card.balance < amount) {
62
+ return null;
63
+ }
64
+ const token = {
65
+ token: `temp_auth_${crypto_1.default.randomBytes(4).toString("hex")}`,
66
+ card_alias: cardAlias,
67
+ amount,
68
+ merchant,
69
+ created_at: Date.now(),
70
+ ttl_seconds: ttlSeconds,
71
+ used: false,
72
+ };
73
+ tokenStore.set(token.token, token);
74
+ return token;
75
+ }
76
+ /**
77
+ * Resolve a token to real card data.
78
+ * CRITICAL: The returned CardData object lives ONLY in RAM.
79
+ * It must NEVER be logged, serialized, or returned to the AI.
80
+ */
81
+ function resolveToken(tokenId) {
82
+ const token = tokenStore.get(tokenId);
83
+ if (!token) {
84
+ return null; // Token doesn't exist
85
+ }
86
+ if (token.used) {
87
+ return null; // Already burned
88
+ }
89
+ // Check TTL expiration
90
+ const age = (Date.now() - token.created_at) / 1000;
91
+ if (age > token.ttl_seconds) {
92
+ tokenStore.delete(tokenId); // Expired, clean up
93
+ return null;
94
+ }
95
+ const card = cardVault.get(token.card_alias);
96
+ if (!card) {
97
+ return null;
98
+ }
99
+ // Return card data (RAM only - never log this!)
100
+ return {
101
+ number: card.number,
102
+ exp_month: card.exp_month,
103
+ exp_year: card.exp_year,
104
+ cvv: card.cvv,
105
+ name: card.name,
106
+ };
107
+ }
108
+ /**
109
+ * Burn a token after use - makes it permanently invalid.
110
+ * Also deducts the amount from the card balance.
111
+ */
112
+ function burnToken(tokenId) {
113
+ const token = tokenStore.get(tokenId);
114
+ if (!token)
115
+ return false;
116
+ token.used = true;
117
+ // Deduct balance
118
+ const card = cardVault.get(token.card_alias);
119
+ if (card) {
120
+ card.balance -= token.amount;
121
+ }
122
+ // Schedule complete removal from memory
123
+ setTimeout(() => {
124
+ tokenStore.delete(tokenId);
125
+ }, 5000);
126
+ return true;
127
+ }
128
+ /**
129
+ * Check balance of a card (safe to expose to AI).
130
+ */
131
+ function getBalance(cardAlias) {
132
+ const card = cardVault.get(cardAlias);
133
+ if (!card)
134
+ return null;
135
+ return { balance: card.balance, currency: card.currency };
136
+ }
137
+ /**
138
+ * List available card aliases (safe to expose to AI - no sensitive data).
139
+ */
140
+ function listCards() {
141
+ const result = [];
142
+ for (const card of cardVault.values()) {
143
+ result.push({
144
+ alias: card.alias,
145
+ balance: card.balance,
146
+ currency: card.currency,
147
+ });
148
+ }
149
+ return result;
150
+ }
@@ -0,0 +1,6 @@
1
+ import type { CardData, PaymentResult } from "./types.js";
2
+ /**
3
+ * Detects and fills credit card form fields on a checkout page.
4
+ * Card data exists ONLY in RAM and is wiped after injection.
5
+ */
6
+ export declare function fillCheckoutForm(checkoutUrl: string, cardData: CardData): Promise<PaymentResult>;
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ // Playwright Bridge - The "Invisible Hand"
3
+ // Securely injects card data into checkout forms without exposing it to AI
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.fillCheckoutForm = fillCheckoutForm;
6
+ const playwright_1 = require("playwright");
7
+ /**
8
+ * Detects and fills credit card form fields on a checkout page.
9
+ * Card data exists ONLY in RAM and is wiped after injection.
10
+ */
11
+ async function fillCheckoutForm(checkoutUrl, cardData) {
12
+ const browser = await playwright_1.chromium.launch({ headless: true });
13
+ const context = await browser.newContext();
14
+ const page = await context.newPage();
15
+ try {
16
+ await page.goto(checkoutUrl, { waitUntil: "networkidle", timeout: 30000 });
17
+ // ============================================================
18
+ // STRATEGY 1: Standard HTML form fields
19
+ // ============================================================
20
+ const standardSelectors = {
21
+ number: [
22
+ 'input[name="cardnumber"]',
23
+ 'input[name="card-number"]',
24
+ 'input[name="cc-number"]',
25
+ 'input[autocomplete="cc-number"]',
26
+ 'input[data-elements-stable-field-name="cardNumber"]',
27
+ 'input[placeholder*="card number" i]',
28
+ 'input[placeholder*="Card number" i]',
29
+ 'input[aria-label*="card number" i]',
30
+ ],
31
+ expiry: [
32
+ 'input[name="exp-date"]',
33
+ 'input[name="cc-exp"]',
34
+ 'input[autocomplete="cc-exp"]',
35
+ 'input[placeholder*="MM / YY" i]',
36
+ 'input[placeholder*="MM/YY" i]',
37
+ 'input[aria-label*="expir" i]',
38
+ ],
39
+ exp_month: [
40
+ 'input[name="exp-month"]',
41
+ 'select[name="exp-month"]',
42
+ 'input[autocomplete="cc-exp-month"]',
43
+ ],
44
+ exp_year: [
45
+ 'input[name="exp-year"]',
46
+ 'select[name="exp-year"]',
47
+ 'input[autocomplete="cc-exp-year"]',
48
+ ],
49
+ cvv: [
50
+ 'input[name="cvc"]',
51
+ 'input[name="cvv"]',
52
+ 'input[name="cc-csc"]',
53
+ 'input[autocomplete="cc-csc"]',
54
+ 'input[placeholder*="CVC" i]',
55
+ 'input[placeholder*="CVV" i]',
56
+ 'input[aria-label*="security code" i]',
57
+ ],
58
+ name: [
59
+ 'input[name="ccname"]',
60
+ 'input[name="cc-name"]',
61
+ 'input[autocomplete="cc-name"]',
62
+ 'input[placeholder*="name on card" i]',
63
+ 'input[aria-label*="name on card" i]',
64
+ ],
65
+ };
66
+ // Try to fill each field
67
+ let filledFields = 0;
68
+ // Card Number
69
+ for (const selector of standardSelectors.number) {
70
+ const el = await page.$(selector);
71
+ if (el) {
72
+ await el.fill(cardData.number);
73
+ filledFields++;
74
+ break;
75
+ }
76
+ }
77
+ // Expiry (combined MM/YY format)
78
+ let expiryFilled = false;
79
+ for (const selector of standardSelectors.expiry) {
80
+ const el = await page.$(selector);
81
+ if (el) {
82
+ await el.fill(`${cardData.exp_month}/${cardData.exp_year.slice(-2)}`);
83
+ filledFields++;
84
+ expiryFilled = true;
85
+ break;
86
+ }
87
+ }
88
+ // Expiry (separate month/year fields)
89
+ if (!expiryFilled) {
90
+ for (const selector of standardSelectors.exp_month) {
91
+ const el = await page.$(selector);
92
+ if (el) {
93
+ await el.fill(cardData.exp_month);
94
+ filledFields++;
95
+ break;
96
+ }
97
+ }
98
+ for (const selector of standardSelectors.exp_year) {
99
+ const el = await page.$(selector);
100
+ if (el) {
101
+ await el.fill(cardData.exp_year);
102
+ filledFields++;
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ // CVV
108
+ for (const selector of standardSelectors.cvv) {
109
+ const el = await page.$(selector);
110
+ if (el) {
111
+ await el.fill(cardData.cvv);
112
+ filledFields++;
113
+ break;
114
+ }
115
+ }
116
+ // Name on Card
117
+ for (const selector of standardSelectors.name) {
118
+ const el = await page.$(selector);
119
+ if (el) {
120
+ await el.fill(cardData.name);
121
+ filledFields++;
122
+ break;
123
+ }
124
+ }
125
+ // ============================================================
126
+ // STRATEGY 2: Stripe Elements (iframe-based)
127
+ // ============================================================
128
+ if (filledFields === 0) {
129
+ const stripeFrames = page.frames().filter((f) => f.url().includes("js.stripe.com"));
130
+ for (const frame of stripeFrames) {
131
+ const cardInput = await frame.$('input[name="cardnumber"]');
132
+ if (cardInput) {
133
+ await cardInput.fill(cardData.number);
134
+ filledFields++;
135
+ }
136
+ const expInput = await frame.$('input[name="exp-date"]');
137
+ if (expInput) {
138
+ await expInput.fill(`${cardData.exp_month}${cardData.exp_year.slice(-2)}`);
139
+ filledFields++;
140
+ }
141
+ const cvcInput = await frame.$('input[name="cvc"]');
142
+ if (cvcInput) {
143
+ await cvcInput.fill(cardData.cvv);
144
+ filledFields++;
145
+ }
146
+ }
147
+ }
148
+ if (filledFields === 0) {
149
+ return {
150
+ success: false,
151
+ message: "Could not detect any credit card fields on this page. The checkout form may use an unsupported format.",
152
+ };
153
+ }
154
+ // ============================================================
155
+ // LOOK FOR "PAY" / "SUBMIT" BUTTON
156
+ // ============================================================
157
+ const payButtonSelectors = [
158
+ 'button[type="submit"]',
159
+ 'button:has-text("Pay")',
160
+ 'button:has-text("Submit")',
161
+ 'button:has-text("Place order")',
162
+ 'button:has-text("Complete")',
163
+ 'input[type="submit"]',
164
+ ];
165
+ let clicked = false;
166
+ for (const selector of payButtonSelectors) {
167
+ const btn = await page.$(selector);
168
+ if (btn && (await btn.isVisible())) {
169
+ await btn.click();
170
+ clicked = true;
171
+ break;
172
+ }
173
+ }
174
+ // Wait for navigation or response
175
+ if (clicked) {
176
+ await page.waitForTimeout(3000);
177
+ }
178
+ const receiptId = `rcpt_${Date.now().toString(36)}`;
179
+ return {
180
+ success: true,
181
+ message: `Payment form filled and submitted successfully. ${filledFields} fields injected.`,
182
+ receipt_id: receiptId,
183
+ };
184
+ }
185
+ catch (error) {
186
+ const errMsg = error instanceof Error ? error.message : String(error);
187
+ return {
188
+ success: false,
189
+ message: `Payment failed: ${errMsg}`,
190
+ };
191
+ }
192
+ finally {
193
+ // ============================================================
194
+ // RAM WIPE - Critical security step
195
+ // ============================================================
196
+ // Overwrite card data with zeros before dereferencing
197
+ cardData.number = "0000000000000000";
198
+ cardData.cvv = "000";
199
+ cardData.exp_month = "00";
200
+ cardData.exp_year = "0000";
201
+ cardData.name = "";
202
+ await browser.close();
203
+ }
204
+ }
@@ -0,0 +1,13 @@
1
+ import type { CardData, PaymentToken } 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 issueTokenRemote(cardAlias: string, amount: number, merchant: string, ttlSeconds?: number): Promise<PaymentToken | null>;
12
+ export declare function resolveTokenRemote(tokenId: string): CardData | null;
13
+ export declare function burnTokenRemote(tokenId: string): Promise<boolean>;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ // Z-ZERO Supabase Backend
3
+ // Replaces mock_backend.ts - connects to real Supabase database
4
+ // Card data is looked up from the 'cards' table via Z_ZERO_API_KEY
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.burnTokenRemote = burnTokenRemote;
14
+ const supabase_js_1 = require("@supabase/supabase-js");
15
+ const crypto_1 = __importDefault(require("crypto"));
16
+ // ============================================================
17
+ // SUPABASE CLIENT (reads env vars at runtime)
18
+ // ============================================================
19
+ const SUPABASE_URL = process.env.Z_ZERO_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || "";
20
+ const SUPABASE_ANON_KEY = process.env.Z_ZERO_SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
21
+ const API_KEY = process.env.Z_ZERO_API_KEY || "";
22
+ if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
23
+ console.error("❌ Missing Z_ZERO_SUPABASE_URL or Z_ZERO_SUPABASE_ANON_KEY env vars");
24
+ }
25
+ if (!API_KEY) {
26
+ console.error("⚠️ Missing Z_ZERO_API_KEY — some tools will be limited");
27
+ }
28
+ const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY);
29
+ // ============================================================
30
+ // IN-MEMORY TOKEN STORE (tokens never hit the database)
31
+ // ============================================================
32
+ const tokenStore = new Map();
33
+ // ============================================================
34
+ // RESOLVE API KEY → USER + WALLET INFO
35
+ // ============================================================
36
+ async function resolveApiKey() {
37
+ if (!API_KEY)
38
+ return null;
39
+ // Lookup card by its stored key (card_number_encrypted stores the api key for now)
40
+ const { data: card, error } = await supabase
41
+ .from("cards")
42
+ .select("id, alias, user_id, allocated_limit_usd, is_active")
43
+ .eq("card_number_encrypted", API_KEY)
44
+ .eq("is_active", true)
45
+ .single();
46
+ if (error || !card) {
47
+ console.error("API Key not found or card is inactive:", error?.message);
48
+ return null;
49
+ }
50
+ // Get wallet balance for this user
51
+ const { data: wallet } = await supabase
52
+ .from("wallets")
53
+ .select("balance")
54
+ .eq("user_id", card.user_id)
55
+ .single();
56
+ return {
57
+ userId: card.user_id,
58
+ walletBalance: Number(wallet?.balance || 0),
59
+ cardId: card.id,
60
+ cardAlias: card.alias,
61
+ };
62
+ }
63
+ // ============================================================
64
+ // PUBLIC API (mirrors mock_backend.ts interface)
65
+ // ============================================================
66
+ async function listCardsRemote() {
67
+ const info = await resolveApiKey();
68
+ if (!info)
69
+ return [];
70
+ return [{
71
+ alias: info.cardAlias,
72
+ balance: info.walletBalance,
73
+ currency: "USD",
74
+ }];
75
+ }
76
+ async function getBalanceRemote(cardAlias) {
77
+ const info = await resolveApiKey();
78
+ if (!info || info.cardAlias !== cardAlias)
79
+ return null;
80
+ return { balance: info.walletBalance, currency: "USD" };
81
+ }
82
+ async function issueTokenRemote(cardAlias, amount, merchant, ttlSeconds = 900) {
83
+ const info = await resolveApiKey();
84
+ if (!info)
85
+ return null;
86
+ if (info.cardAlias !== cardAlias)
87
+ return null;
88
+ if (info.walletBalance < amount)
89
+ return null;
90
+ const token = {
91
+ token: `temp_auth_${crypto_1.default.randomBytes(6).toString("hex")}`,
92
+ card_alias: cardAlias,
93
+ amount,
94
+ merchant,
95
+ created_at: Date.now(),
96
+ ttl_seconds: ttlSeconds,
97
+ used: false,
98
+ };
99
+ tokenStore.set(token.token, token);
100
+ return token;
101
+ }
102
+ function resolveTokenRemote(tokenId) {
103
+ const token = tokenStore.get(tokenId);
104
+ if (!token || token.used)
105
+ return null;
106
+ const age = (Date.now() - token.created_at) / 1000;
107
+ if (age > token.ttl_seconds) {
108
+ tokenStore.delete(tokenId);
109
+ return null;
110
+ }
111
+ // In Phase 2 this will return a real Airwallex JIT card
112
+ // For now, returns a Stripe test card for sandbox testing
113
+ return {
114
+ number: "4242424242424242",
115
+ exp_month: "12",
116
+ exp_year: "2030",
117
+ cvv: "123",
118
+ name: "Z-ZERO Agent",
119
+ };
120
+ }
121
+ async function burnTokenRemote(tokenId) {
122
+ const token = tokenStore.get(tokenId);
123
+ if (!token)
124
+ return false;
125
+ token.used = true;
126
+ // Deduct from Supabase wallet balance
127
+ const info = await resolveApiKey();
128
+ if (info) {
129
+ const newBalance = info.walletBalance - token.amount;
130
+ await supabase
131
+ .from("wallets")
132
+ .update({ balance: newBalance })
133
+ .eq("user_id", info.userId);
134
+ // Log the transaction
135
+ await supabase.from("transactions").insert({
136
+ card_id: info.cardId,
137
+ amount: token.amount,
138
+ merchant: token.merchant,
139
+ status: "SUCCESS",
140
+ });
141
+ }
142
+ setTimeout(() => tokenStore.delete(tokenId), 5000);
143
+ return true;
144
+ }
@@ -0,0 +1,32 @@
1
+ export interface VirtualCard {
2
+ alias: string;
3
+ number: string;
4
+ exp_month: string;
5
+ exp_year: string;
6
+ cvv: string;
7
+ name: string;
8
+ balance: number;
9
+ currency: string;
10
+ }
11
+ export interface PaymentToken {
12
+ token: string;
13
+ card_alias: string;
14
+ amount: number;
15
+ merchant: string;
16
+ created_at: number;
17
+ ttl_seconds: number;
18
+ used: boolean;
19
+ }
20
+ export interface CardData {
21
+ number: string;
22
+ exp_month: string;
23
+ exp_year: string;
24
+ cvv: string;
25
+ name: string;
26
+ }
27
+ export interface PaymentResult {
28
+ success: boolean;
29
+ message: string;
30
+ receipt_id?: string;
31
+ amount?: number;
32
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Shared TypeScript types for AI Virtual Card MCP Server
3
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "z-zero-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "Z-ZERO MCP Server — Secure JIT Virtual Card Payment tools for AI Agents (Claude, AutoGPT, CrewAI)",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "z-zero-mcp-server": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "npx tsx src/index.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "ai-agent",
19
+ "virtual-card",
20
+ "payment",
21
+ "z-zero",
22
+ "clawcard",
23
+ "jit",
24
+ "fintech"
25
+ ],
26
+ "author": "Z-ZERO",
27
+ "license": "MIT",
28
+ "files": [
29
+ "dist/",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.12.1",
34
+ "@supabase/supabase-js": "^2.49.4",
35
+ "dotenv": "^16.5.0",
36
+ "playwright": "^1.52.0",
37
+ "zod": "^3.25.11"
38
+ },
39
+ "devDependencies": {
40
+ "typescript": "^5.8.3",
41
+ "tsx": "^4.19.4",
42
+ "@types/node": "^22.15.21"
43
+ }
44
+ }