x402-bazaar 1.1.0 → 1.2.1

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/bin/cli.js CHANGED
@@ -21,7 +21,7 @@ const program = new Command();
21
21
  program
22
22
  .name('x402-bazaar')
23
23
  .description(chalk.hex('#FF9900')('x402 Bazaar') + ' — Connect your AI agent to the marketplace in one command')
24
- .version('1.1.0');
24
+ .version('1.2.1');
25
25
 
26
26
  program
27
27
  .command('init')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-bazaar",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI to set up x402 Bazaar MCP server for AI agents. One command to connect your agent to the marketplace.",
5
5
  "type": "module",
6
6
  "main": "bin/cli.js",
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync } from 'fs';
5
5
  import { join, dirname, resolve } from 'path';
6
6
  import { execSync } from 'child_process';
7
+ import { randomBytes } from 'crypto';
7
8
  import { fileURLToPath } from 'url';
8
9
  import { log } from '../utils/logger.js';
9
10
  import { isInteractive, promptOrDefault, printNonInteractiveHint } from '../utils/prompt.js';
@@ -126,8 +127,18 @@ export async function initCommand(options) {
126
127
  copyFileSync(mcpSource, mcpServerDest);
127
128
  spinner.text = 'Copied mcp-server.mjs from local project...';
128
129
  } else {
129
- spinner.text = 'Generating MCP server file...';
130
- writeFileSync(mcpServerDest, generateMcpServerFile());
130
+ spinner.text = 'Downloading MCP server from GitHub...';
131
+ try {
132
+ const dlRes = await fetch('https://raw.githubusercontent.com/Wintyx57/x402-backend/main/mcp-server.mjs');
133
+ if (!dlRes.ok) throw new Error(`HTTP ${dlRes.status}`);
134
+ writeFileSync(mcpServerDest, await dlRes.text());
135
+ } catch (dlErr) {
136
+ spinner.fail(`Could not download MCP server: ${dlErr.message}`);
137
+ log.error('Download the file manually from:');
138
+ log.dim(' https://github.com/Wintyx57/x402-backend/blob/main/mcp-server.mjs');
139
+ log.dim(` Save it to: ${mcpServerDest}`);
140
+ process.exit(1);
141
+ }
131
142
  }
132
143
 
133
144
  // Create package.json for the MCP server runtime
@@ -135,14 +146,16 @@ export async function initCommand(options) {
135
146
  if (!existsSync(pkgJsonPath)) {
136
147
  writeFileSync(pkgJsonPath, JSON.stringify({
137
148
  name: 'x402-bazaar-mcp',
138
- version: '1.0.0',
149
+ version: '2.0.0',
139
150
  type: 'module',
140
151
  private: true,
141
152
  dependencies: {
142
- '@coinbase/coinbase-sdk': '^0.25.0',
153
+ 'viem': '^2.0.0',
143
154
  '@modelcontextprotocol/sdk': '^1.26.0',
144
155
  'dotenv': '^17.2.4',
145
156
  'zod': '^4.3.6',
157
+ 'ed2curve': '^0.3.0',
158
+ '@scure/bip32': '^1.6.0',
146
159
  },
147
160
  }, null, 2));
148
161
  }
@@ -183,9 +196,9 @@ export async function initCommand(options) {
183
196
  console.log('');
184
197
 
185
198
  let walletMode = 'readonly';
199
+ let agentPrivateKey = '';
186
200
  let coinbaseApiKey = '';
187
201
  let coinbaseApiSecret = '';
188
- let seedPath = '';
189
202
  let maxBudget = '1.00';
190
203
  let network = 'mainnet';
191
204
  let serverUrl = options.serverUrl || 'https://x402-api.onrender.com';
@@ -200,31 +213,92 @@ export async function initCommand(options) {
200
213
  message: 'How do you want to configure payments?',
201
214
  choices: [
202
215
  {
203
- name: `${chalk.bold('I have Coinbase API keys')} — Full access (search, register, pay)`,
204
- value: 'existing',
216
+ name: `${chalk.bold('Generate a new wallet')} — Creates a fresh Ethereum wallet automatically (Recommended)`,
217
+ value: 'generate',
205
218
  },
206
219
  {
207
- name: `${chalk.bold('Create a new wallet')} — Guide me through setup`,
208
- value: 'new',
220
+ name: `${chalk.bold('Import private key')} — Use an existing Ethereum private key`,
221
+ value: 'import',
222
+ },
223
+ {
224
+ name: `${chalk.bold('Coinbase API keys')} — Legacy: use Coinbase CDP seed file`,
225
+ value: 'coinbase',
209
226
  },
210
227
  {
211
228
  name: `${chalk.bold('Read-only mode')} — Browse marketplace for free (no payments)`,
212
229
  value: 'readonly',
213
230
  },
214
231
  ],
215
- default: 'readonly',
232
+ default: 'generate',
216
233
  }]);
217
234
 
218
235
  walletMode = mode;
219
236
 
220
- if (mode === 'existing') {
237
+ if (mode === 'generate') {
238
+ agentPrivateKey = '0x' + randomBytes(32).toString('hex');
239
+ log.success('New wallet generated!');
240
+
241
+ // Derive address using viem (installed in step 2)
242
+ let walletAddress = '';
243
+ try {
244
+ walletAddress = execSync(
245
+ `node --input-type=module -e "import{privateKeyToAccount}from'viem/accounts';console.log(privateKeyToAccount('${agentPrivateKey}').address)"`,
246
+ { cwd: installDir, stdio: 'pipe', timeout: 15000 }
247
+ ).toString().trim();
248
+ console.log('');
249
+ log.info(`Wallet address: ${chalk.bold(walletAddress)}`);
250
+ log.dim(` BaseScan: https://basescan.org/address/${walletAddress}`);
251
+ console.log('');
252
+ log.separator();
253
+ log.info(chalk.bold('To activate payments, fund this wallet from MetaMask:'));
254
+ console.log('');
255
+ log.dim(` ${chalk.white('1.')} Open MetaMask and switch to the ${chalk.bold('Base')} network`);
256
+ log.dim(` (Chain ID: 8453 — add it via https://chainlist.org/chain/8453)`);
257
+ log.dim(` ${chalk.white('2.')} Send ${chalk.bold('USDC')} to: ${chalk.hex('#34D399')(walletAddress)}`);
258
+ log.dim(` (Even $1 USDC is enough to start — each API call costs $0.005-$0.05)`);
259
+ log.dim(` ${chalk.white('3.')} Send a tiny bit of ${chalk.bold('ETH')} to the same address for gas`);
260
+ log.dim(` (${chalk.white('~$0.01 of ETH on Base')} is enough for hundreds of transactions)`);
261
+ console.log('');
262
+ log.warn(`IMPORTANT: Send on the ${chalk.bold('Base')} network only — not Ethereum mainnet!`);
263
+ log.separator();
264
+ } catch {
265
+ log.info('Wallet address will be shown when you first use the MCP server.');
266
+ log.dim(' Use the get_wallet_balance tool to see your address.');
267
+ }
268
+ }
269
+
270
+ if (mode === 'import') {
271
+ if (!isInteractive()) {
272
+ log.warn('Cannot enter private key in a non-interactive terminal.');
273
+ log.info('Set AGENT_PRIVATE_KEY in your .env file manually after setup.');
274
+ walletMode = 'readonly';
275
+ } else {
276
+ const { key } = await promptOrDefault([{
277
+ type: 'password',
278
+ name: 'key',
279
+ message: 'Ethereum private key (0x...):',
280
+ mask: '*',
281
+ validate: (v) => {
282
+ const trimmed = v.trim();
283
+ if (trimmed.length === 0) return 'Private key is required';
284
+ const hex = trimmed.startsWith('0x') ? trimmed.slice(2) : trimmed;
285
+ if (!/^[0-9a-fA-F]{64}$/.test(hex)) return 'Invalid private key (expected 64 hex characters)';
286
+ return true;
287
+ },
288
+ }]);
289
+ agentPrivateKey = key.trim().startsWith('0x') ? key.trim() : `0x${key.trim()}`;
290
+ log.success('Private key imported.');
291
+ }
292
+ }
293
+
294
+ if (mode === 'coinbase') {
221
295
  if (!isInteractive()) {
222
296
  log.warn('Cannot enter API credentials in a non-interactive terminal.');
223
297
  log.info('Falling back to read-only mode.');
224
- log.dim(' To configure wallet, run in a standalone terminal:');
225
- log.dim(' npx x402-bazaar init');
226
298
  walletMode = 'readonly';
227
299
  } else {
300
+ log.dim(' Legacy mode: requires Coinbase CDP API keys + agent-seed.json');
301
+ console.log('');
228
302
  const walletAnswers = await promptOrDefault([
229
303
  {
230
304
  type: 'input',
@@ -243,43 +317,12 @@ export async function initCommand(options) {
243
317
  coinbaseApiKey = walletAnswers.coinbaseApiKey.trim();
244
318
  coinbaseApiSecret = walletAnswers.coinbaseApiSecret.trim();
245
319
 
246
- // Check if agent-seed.json exists
247
320
  const existingSeed = join(installDir, 'agent-seed.json');
248
321
  if (!existsSync(existingSeed)) {
249
- log.warn('No agent-seed.json found. You will need to create a wallet.');
250
- log.dim(' Run: cd "' + installDir + '" && node -e "const{Coinbase,Wallet}=require(\'@coinbase/coinbase-sdk\');...');
251
- log.dim(' Or copy your existing agent-seed.json to: ' + installDir);
252
- }
253
- }
254
- }
255
-
256
- if (mode === 'new') {
257
- console.log('');
258
- log.info('To get Coinbase API keys:');
259
- console.log('');
260
- log.dim(' 1. Go to https://portal.cdp.coinbase.com/');
261
- log.dim(' 2. Create a project (free)');
262
- log.dim(' 3. Go to "API Keys" and generate a key pair');
263
- log.dim(' 4. Save both the API Key Name and the Private Key');
264
- log.dim(' 5. Run npx x402-bazaar init again with your keys');
265
- console.log('');
266
-
267
- if (!isInteractive()) {
268
- log.info('Continuing in read-only mode (non-interactive terminal).');
269
- walletMode = 'readonly';
270
- } else {
271
- const { proceed } = await promptOrDefault([{
272
- type: 'confirm',
273
- name: 'proceed',
274
- message: 'Continue in read-only mode for now?',
275
- default: true,
276
- }]);
277
-
278
- if (!proceed) {
279
- log.info('Run npx x402-bazaar init when you have your API keys.');
280
- process.exit(0);
322
+ log.warn('No agent-seed.json found.');
323
+ log.dim(' Copy your existing agent-seed.json to: ' + installDir);
324
+ log.dim(' Or re-run init and choose "Generate a new wallet" instead.');
281
325
  }
282
- walletMode = 'readonly';
283
326
  }
284
327
  }
285
328
 
@@ -315,7 +358,6 @@ export async function initCommand(options) {
315
358
 
316
359
  network = configAnswers.network;
317
360
  maxBudget = configAnswers.maxBudget;
318
- seedPath = join(installDir, 'agent-seed.json');
319
361
  }
320
362
 
321
363
  console.log('');
@@ -330,9 +372,9 @@ export async function initCommand(options) {
330
372
  serverUrl,
331
373
  maxBudget,
332
374
  network,
375
+ agentPrivateKey,
333
376
  coinbaseApiKey,
334
377
  coinbaseApiSecret,
335
- seedPath,
336
378
  readOnly: walletMode === 'readonly',
337
379
  });
338
380
 
@@ -342,9 +384,9 @@ export async function initCommand(options) {
342
384
  serverUrl,
343
385
  maxBudget,
344
386
  network,
387
+ agentPrivateKey,
345
388
  coinbaseApiKey,
346
389
  coinbaseApiSecret,
347
- seedPath,
348
390
  });
349
391
  const envPath = join(installDir, '.env');
350
392
  writeFileSync(envPath, envContent);
@@ -409,17 +451,29 @@ export async function initCommand(options) {
409
451
  'claude-code': 'Restart Claude Code to activate the MCP server.',
410
452
  };
411
453
 
454
+ const walletLabel = walletMode === 'readonly'
455
+ ? 'Read-only (no payments)'
456
+ : walletMode === 'generate'
457
+ ? 'New wallet (needs funding)'
458
+ : 'Configured';
459
+
412
460
  const summaryLines = [
413
461
  `Environment: ${targetEnv.label}`,
414
462
  `Install dir: ${installDir}`,
415
463
  `Server: ${serverUrl}`,
416
464
  `Network: ${network === 'mainnet' ? 'Base Mainnet' : 'Base Sepolia'}`,
417
465
  `Budget limit: ${maxBudget} USDC / session`,
418
- `Wallet: ${walletMode === 'readonly' ? 'Read-only (no payments)' : 'Configured'}`,
466
+ `Wallet: ${walletLabel}`,
419
467
  `Services: ${serviceCount > 0 ? serviceCount + ' available' : 'check with npx x402-bazaar status'}`,
420
468
  '',
421
469
  restartMsg[targetEnv.name] || 'Configure your AI client with the generated JSON above.',
422
470
  '',
471
+ ...(walletMode === 'generate' ? [
472
+ 'Before your agent can pay for APIs:',
473
+ ' 1. Send USDC + a little ETH to your wallet on Base',
474
+ ' 2. Restart your IDE',
475
+ '',
476
+ ] : []),
423
477
  'Then try asking your agent:',
424
478
  ' "Search for weather APIs on x402 Bazaar"',
425
479
  ' "List all available services on the marketplace"',
@@ -487,319 +541,3 @@ function writeConfig(envInfo, newConfig) {
487
541
  }
488
542
  }
489
543
 
490
- /**
491
- * Generate a standalone MCP server file.
492
- * Used as fallback when the local x402-bazaar project is not found.
493
- */
494
- function generateMcpServerFile() {
495
- return `import 'dotenv/config';
496
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
497
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
498
- import { z } from 'zod';
499
- import { createRequire } from 'module';
500
-
501
- const require = createRequire(import.meta.url);
502
- const { Coinbase, Wallet } = require('@coinbase/coinbase-sdk');
503
-
504
- // ─── Config ──────────────────────────────────────────────────────────
505
- const SERVER_URL = process.env.X402_SERVER_URL || 'https://x402-api.onrender.com';
506
- const MAX_BUDGET = parseFloat(process.env.MAX_BUDGET_USDC || '1.00');
507
- const NETWORK = process.env.NETWORK || 'mainnet';
508
- const explorerBase = NETWORK === 'testnet'
509
- ? 'https://sepolia.basescan.org'
510
- : 'https://basescan.org';
511
- const networkLabel = NETWORK === 'testnet' ? 'Base Sepolia' : 'Base Mainnet';
512
-
513
- // ─── Budget Tracking ─────────────────────────────────────────────────
514
- let sessionSpending = 0;
515
- const sessionPayments = [];
516
-
517
- // ─── Wallet ──────────────────────────────────────────────────────────
518
- let wallet = null;
519
- let walletReady = false;
520
-
521
- async function initWallet() {
522
- if (walletReady) return;
523
-
524
- Coinbase.configure({
525
- apiKeyName: process.env.COINBASE_API_KEY,
526
- privateKey: process.env.COINBASE_API_SECRET,
527
- });
528
-
529
- const seedPath = process.env.AGENT_SEED_PATH || 'agent-seed.json';
530
- const fs = await import('fs');
531
- const seedData = JSON.parse(fs.readFileSync(seedPath, 'utf-8'));
532
- const seedWalletId = Object.keys(seedData)[0];
533
-
534
- wallet = await Wallet.fetch(seedWalletId);
535
- await wallet.loadSeed(seedPath);
536
- walletReady = true;
537
- }
538
-
539
- // ─── x402 Payment Flow ──────────────────────────────────────────────
540
- async function payAndRequest(url, options = {}) {
541
- const res = await fetch(url, options);
542
- const body = await res.json();
543
-
544
- if (res.status !== 402) {
545
- return body;
546
- }
547
-
548
- // HTTP 402 — Payment Required
549
- const details = body.payment_details;
550
- const cost = parseFloat(details.amount);
551
-
552
- // Budget check
553
- if (sessionSpending + cost > MAX_BUDGET) {
554
- throw new Error(
555
- \`Budget limit reached. Spent: \${sessionSpending.toFixed(2)} USDC / \${MAX_BUDGET.toFixed(2)} USDC limit. \` +
556
- \`This call costs \${cost} USDC. Increase MAX_BUDGET_USDC env var to allow more spending.\`
557
- );
558
- }
559
-
560
- await initWallet();
561
-
562
- const transfer = await wallet.createTransfer({
563
- amount: details.amount,
564
- assetId: Coinbase.assets.Usdc,
565
- destination: details.recipient,
566
- });
567
- const confirmed = await transfer.wait({ timeoutSeconds: 120 });
568
- const txHash = confirmed.getTransactionHash();
569
-
570
- // Track spending
571
- sessionSpending += cost;
572
- sessionPayments.push({
573
- amount: cost,
574
- txHash,
575
- timestamp: new Date().toISOString(),
576
- endpoint: url.replace(SERVER_URL, ''),
577
- });
578
-
579
- // Retry with payment proof
580
- const retryHeaders = { ...options.headers, 'X-Payment-TxHash': txHash };
581
- const retryRes = await fetch(url, { ...options, headers: retryHeaders });
582
- const result = await retryRes.json();
583
-
584
- // Enrich result with payment info
585
- result._payment = {
586
- amount: details.amount,
587
- currency: 'USDC',
588
- txHash,
589
- explorer: \`\${explorerBase}/tx/\${txHash}\`,
590
- session_spent: sessionSpending.toFixed(2),
591
- session_remaining: (MAX_BUDGET - sessionSpending).toFixed(2),
592
- };
593
-
594
- return result;
595
- }
596
-
597
- // ─── MCP Server ─────────────────────────────────────────────────────
598
- const server = new McpServer({
599
- name: 'x402-bazaar',
600
- version: '1.1.0',
601
- });
602
-
603
- // --- Tool: discover_marketplace (FREE) ---
604
- server.tool(
605
- 'discover_marketplace',
606
- 'Discover the x402 Bazaar marketplace. Returns available endpoints, total services, and protocol info. Free — no payment needed.',
607
- {},
608
- async () => {
609
- try {
610
- const res = await fetch(SERVER_URL);
611
- const data = await res.json();
612
- return {
613
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
614
- };
615
- } catch (err) {
616
- return {
617
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
618
- isError: true,
619
- };
620
- }
621
- }
622
- );
623
-
624
- // --- Tool: search_services (0.05 USDC) ---
625
- server.tool(
626
- 'search_services',
627
- \`Search for API services on x402 Bazaar by keyword. Costs 0.05 USDC (paid automatically). Budget: \${MAX_BUDGET.toFixed(2)} USDC per session. Check get_budget_status before calling if unsure about remaining budget.\`,
628
- { query: z.string().describe('Search keyword (e.g. "weather", "crypto", "ai")') },
629
- async ({ query }) => {
630
- try {
631
- const result = await payAndRequest(
632
- \`\${SERVER_URL}/search?q=\${encodeURIComponent(query)}\`
633
- );
634
- return {
635
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
636
- };
637
- } catch (err) {
638
- return {
639
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
640
- isError: true,
641
- };
642
- }
643
- }
644
- );
645
-
646
- // --- Tool: list_services (0.05 USDC) ---
647
- server.tool(
648
- 'list_services',
649
- \`List all API services available on x402 Bazaar. Costs 0.05 USDC (paid automatically). Budget: \${MAX_BUDGET.toFixed(2)} USDC per session.\`,
650
- {},
651
- async () => {
652
- try {
653
- const result = await payAndRequest(\`\${SERVER_URL}/services\`);
654
- return {
655
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
656
- };
657
- } catch (err) {
658
- return {
659
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
660
- isError: true,
661
- };
662
- }
663
- }
664
- );
665
-
666
- // --- Tool: find_tool_for_task (0.05 USDC — smart service lookup) ---
667
- server.tool(
668
- 'find_tool_for_task',
669
- \`Describe what you need in plain English and get the best matching API service ready to call. Returns the single best match with name, URL, price, and usage instructions. Costs 0.05 USDC. Budget: \${MAX_BUDGET.toFixed(2)} USDC per session.\`,
670
- { task: z.string().describe('What you need, in natural language (e.g. "get current weather for a city", "translate text to French")') },
671
- async ({ task }) => {
672
- try {
673
- const stopWords = new Set(['i', 'need', 'want', 'to', 'a', 'an', 'the', 'for', 'of', 'and', 'or', 'in', 'on', 'with', 'that', 'this', 'get', 'find', 'me', 'my', 'some', 'can', 'you', 'do', 'is', 'it', 'be', 'have', 'use', 'please', 'should', 'would', 'could']);
674
- const keywords = task.toLowerCase()
675
- .replace(/[^a-z0-9\\s]/g, '')
676
- .split(/\\s+/)
677
- .filter(w => w.length > 2 && !stopWords.has(w));
678
- const query = keywords.slice(0, 3).join(' ') || task.slice(0, 30);
679
-
680
- const result = await payAndRequest(
681
- \`\${SERVER_URL}/search?q=\${encodeURIComponent(query)}\`
682
- );
683
-
684
- const services = result.data || result.services || [];
685
- if (services.length === 0) {
686
- return {
687
- content: [{ type: 'text', text: JSON.stringify({
688
- found: false,
689
- query_used: query,
690
- message: \`No services found matching "\${task}". Try rephrasing or use search_services with different keywords.\`,
691
- _payment: result._payment,
692
- }, null, 2) }],
693
- };
694
- }
695
-
696
- const best = services[0];
697
- return {
698
- content: [{ type: 'text', text: JSON.stringify({
699
- found: true,
700
- query_used: query,
701
- service: {
702
- name: best.name,
703
- description: best.description,
704
- url: best.url,
705
- price_usdc: best.price_usdc,
706
- tags: best.tags,
707
- },
708
- action: \`Call this API using call_api("\${best.url}"). \${Number(best.price_usdc) === 0 ? 'This API is free.' : \`This API costs \${best.price_usdc} USDC per call.\`}\`,
709
- alternatives_count: services.length - 1,
710
- _payment: result._payment,
711
- }, null, 2) }],
712
- };
713
- } catch (err) {
714
- return {
715
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
716
- isError: true,
717
- };
718
- }
719
- }
720
- );
721
-
722
- // --- Tool: call_api (FREE — calls external APIs) ---
723
- server.tool(
724
- 'call_api',
725
- 'Call an external API URL and return the response. Use this to fetch real data from service URLs discovered on the marketplace. Free — no marketplace payment needed.',
726
- { url: z.string().url().describe('The full API URL to call') },
727
- async ({ url }) => {
728
- try {
729
- const res = await fetch(url);
730
- const text = await res.text();
731
- let parsed;
732
- try {
733
- parsed = JSON.parse(text);
734
- } catch {
735
- parsed = { response: text.slice(0, 5000) };
736
- }
737
- return {
738
- content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }],
739
- };
740
- } catch (err) {
741
- return {
742
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
743
- isError: true,
744
- };
745
- }
746
- }
747
- );
748
-
749
- // --- Tool: get_wallet_balance (FREE) ---
750
- server.tool(
751
- 'get_wallet_balance',
752
- 'Check the USDC balance of the agent wallet on-chain. Free.',
753
- {},
754
- async () => {
755
- try {
756
- await initWallet();
757
- const balance = await wallet.getBalance(Coinbase.assets.Usdc);
758
- const address = (await wallet.getDefaultAddress()).getId();
759
- return {
760
- content: [{
761
- type: 'text',
762
- text: JSON.stringify({
763
- address,
764
- balance_usdc: balance.toString(),
765
- network: networkLabel,
766
- explorer: \`\${explorerBase}/address/\${address}\`,
767
- }, null, 2),
768
- }],
769
- };
770
- } catch (err) {
771
- return {
772
- content: [{ type: 'text', text: \`Error: \${err.message}\` }],
773
- isError: true,
774
- };
775
- }
776
- }
777
- );
778
-
779
- // --- Tool: get_budget_status (FREE) ---
780
- server.tool(
781
- 'get_budget_status',
782
- 'Check the session spending budget. Shows how much USDC has been spent, remaining budget, and a list of all payments made this session. Free — call this before paid requests to verify budget.',
783
- {},
784
- async () => {
785
- return {
786
- content: [{
787
- type: 'text',
788
- text: JSON.stringify({
789
- budget_limit: MAX_BUDGET.toFixed(2) + ' USDC',
790
- spent: sessionSpending.toFixed(2) + ' USDC',
791
- remaining: (MAX_BUDGET - sessionSpending).toFixed(2) + ' USDC',
792
- payments_count: sessionPayments.length,
793
- payments: sessionPayments,
794
- network: networkLabel,
795
- }, null, 2),
796
- }],
797
- };
798
- }
799
- );
800
-
801
- // ─── Start ──────────────────────────────────────────────────────────
802
- const transport = new StdioServerTransport();
803
- await server.connect(transport);
804
- `;
805
- }
@@ -5,11 +5,11 @@ export function generateEnvContent({
5
5
  serverUrl = 'https://x402-api.onrender.com',
6
6
  maxBudget = '1.00',
7
7
  network = 'mainnet',
8
+ agentPrivateKey = '',
8
9
  coinbaseApiKey = '',
9
10
  coinbaseApiSecret = '',
10
- seedPath = 'agent-seed.json',
11
11
  }) {
12
- return `# x402 Bazaar MCP Server Configuration
12
+ let content = `# x402 Bazaar MCP Server Configuration
13
13
  # Generated by: npx x402-bazaar init
14
14
 
15
15
  # Marketplace server URL
@@ -20,13 +20,22 @@ MAX_BUDGET_USDC=${maxBudget}
20
20
 
21
21
  # Network: mainnet or testnet
22
22
  NETWORK=${network}
23
+ `;
23
24
 
24
- # Coinbase Developer Platform API credentials
25
+ if (agentPrivateKey) {
26
+ content += `
27
+ # Agent wallet private key (Ethereum)
28
+ # WARNING: Keep this secret! Never commit to git.
29
+ AGENT_PRIVATE_KEY=${agentPrivateKey}
30
+ `;
31
+ } else if (coinbaseApiKey) {
32
+ content += `
33
+ # Coinbase Developer Platform API credentials (legacy mode)
25
34
  # Get yours at: https://portal.cdp.coinbase.com/
26
35
  COINBASE_API_KEY=${coinbaseApiKey}
27
36
  COINBASE_API_SECRET=${coinbaseApiSecret}
28
-
29
- # Path to the agent wallet seed file
30
- AGENT_SEED_PATH=${seedPath}
31
37
  `;
38
+ }
39
+
40
+ return content;
32
41
  }
@@ -10,16 +10,12 @@ export function generateMcpConfig({
10
10
  serverUrl = 'https://x402-api.onrender.com',
11
11
  maxBudget = '1.00',
12
12
  network = 'mainnet',
13
+ agentPrivateKey = '',
13
14
  coinbaseApiKey = '',
14
15
  coinbaseApiSecret = '',
15
- seedPath = '',
16
16
  readOnly = false,
17
17
  }) {
18
- const os = platform();
19
- const sep = os === 'win32' ? '\\' : '/';
20
-
21
18
  const mcpServerPath = join(installDir, 'mcp-server.mjs');
22
- const defaultSeedPath = seedPath || join(installDir, 'agent-seed.json');
23
19
 
24
20
  const env = {
25
21
  X402_SERVER_URL: serverUrl,
@@ -28,9 +24,12 @@ export function generateMcpConfig({
28
24
  };
29
25
 
30
26
  if (!readOnly) {
31
- env.COINBASE_API_KEY = coinbaseApiKey || 'YOUR_COINBASE_API_KEY';
32
- env.COINBASE_API_SECRET = coinbaseApiSecret || 'YOUR_COINBASE_API_SECRET';
33
- env.AGENT_SEED_PATH = defaultSeedPath;
27
+ if (agentPrivateKey) {
28
+ env.AGENT_PRIVATE_KEY = agentPrivateKey;
29
+ } else if (coinbaseApiKey) {
30
+ env.COINBASE_API_KEY = coinbaseApiKey;
31
+ env.COINBASE_API_SECRET = coinbaseApiSecret;
32
+ }
34
33
  }
35
34
 
36
35
  const serverEntry = {
@@ -41,27 +40,6 @@ export function generateMcpConfig({
41
40
 
42
41
  // Format for the target environment
43
42
  switch (environment) {
44
- case 'claude-desktop':
45
- return {
46
- mcpServers: {
47
- 'x402-bazaar': serverEntry,
48
- },
49
- };
50
-
51
- case 'cursor':
52
- return {
53
- mcpServers: {
54
- 'x402-bazaar': serverEntry,
55
- },
56
- };
57
-
58
- case 'claude-code':
59
- return {
60
- mcpServers: {
61
- 'x402-bazaar': serverEntry,
62
- },
63
- };
64
-
65
43
  case 'vscode-continue':
66
44
  return {
67
45
  models: [],