@x402storage/mcp 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/dist/config.d.ts CHANGED
@@ -1,18 +1,45 @@
1
1
  /**
2
- * Environment validation for x402store MCP server
2
+ * Environment validation and multi-wallet config for x402store MCP server
3
3
  */
4
- export type WalletType = 'evm' | 'solana';
4
+ export type WalletType = 'evm' | 'sol';
5
+ /**
6
+ * Config file format for multi-wallet support
7
+ */
8
+ export interface X402Config {
9
+ activeWallet: WalletType;
10
+ wallets: {
11
+ evm?: string;
12
+ sol?: string;
13
+ };
14
+ }
5
15
  /**
6
16
  * Detects wallet type from private key format
7
17
  */
8
18
  export declare function detectWalletType(key: string): WalletType;
9
19
  /**
10
- * Validates required environment variables.
11
- * Returns error message if X402_PRIVATE_KEY is missing, null if valid.
20
+ * Loads config from ~/.x402-config.json
21
+ * Returns null if config doesn't exist
22
+ */
23
+ export declare function loadConfig(): X402Config | null;
24
+ /**
25
+ * Saves config to ~/.x402-config.json
26
+ */
27
+ export declare function saveConfig(config: X402Config): void;
28
+ /**
29
+ * Gets the active wallet's private key
30
+ * Returns null if no wallet is configured
31
+ */
32
+ export declare function getActiveWallet(): {
33
+ type: WalletType;
34
+ privateKey: string;
35
+ } | null;
36
+ /**
37
+ * Validates required environment variables or config.
38
+ * Returns error message if no wallet configured, null if valid.
12
39
  */
13
40
  export declare function validateEnvironment(): string | null;
14
41
  /**
15
- * Gets the private key from environment.
42
+ * Gets the private key from environment or config.
16
43
  * Should only be called after validateEnvironment() returns null.
17
44
  */
18
45
  export declare function getPrivateKey(): string;
@@ -23,8 +50,31 @@ export interface WalletInfo {
23
50
  address: string;
24
51
  balance: string;
25
52
  chain: string;
53
+ type: WalletType;
54
+ isActive: boolean;
26
55
  }
27
56
  /**
28
57
  * Gets wallet address and balance from the configured private key
29
58
  */
30
59
  export declare function getWalletInfo(): Promise<WalletInfo>;
60
+ /**
61
+ * Gets info for all configured wallets
62
+ */
63
+ export declare function getAllWalletInfo(): Promise<WalletInfo[]>;
64
+ /**
65
+ * Sets the active wallet type
66
+ * Returns error message if wallet type not configured, null on success
67
+ */
68
+ export declare function setActiveWallet(type: WalletType): string | null;
69
+ /**
70
+ * Checks if legacy wallet file exists and returns migration info
71
+ */
72
+ export declare function checkLegacyWallet(): {
73
+ exists: boolean;
74
+ type?: WalletType;
75
+ privateKey?: string;
76
+ };
77
+ /**
78
+ * Gets the config file path (for CLI output)
79
+ */
80
+ export declare function getConfigPath(): string;
package/dist/config.js CHANGED
@@ -1,54 +1,202 @@
1
1
  /**
2
- * Environment validation for x402store MCP server
2
+ * Environment validation and multi-wallet config for x402store MCP server
3
3
  */
4
4
  import { privateKeyToAccount } from 'viem/accounts';
5
5
  import { createPublicClient, http, formatUnits } from 'viem';
6
6
  import { base } from 'viem/chains';
7
7
  import { Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js';
8
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ const CONFIG_PATH = join(homedir(), '.x402-config.json');
12
+ const LEGACY_WALLET_PATH = join(homedir(), '.x402-wallet');
8
13
  /**
9
14
  * Detects wallet type from private key format
10
15
  */
11
16
  export function detectWalletType(key) {
12
17
  const trimmed = key.trim();
13
- // EVM keys start with 0x
14
- if (trimmed.startsWith('0x')) {
15
- return 'evm';
16
- }
17
18
  // Solana keys are JSON arrays
18
19
  if (trimmed.startsWith('[')) {
19
- return 'solana';
20
+ return 'sol';
20
21
  }
21
- // Default to EVM for other formats
22
+ // Default to EVM for other formats (including 0x prefix)
22
23
  return 'evm';
23
24
  }
24
25
  /**
25
- * Validates required environment variables.
26
- * Returns error message if X402_PRIVATE_KEY is missing, null if valid.
26
+ * Loads config from ~/.x402-config.json
27
+ * Returns null if config doesn't exist
28
+ */
29
+ export function loadConfig() {
30
+ if (!existsSync(CONFIG_PATH)) {
31
+ return null;
32
+ }
33
+ try {
34
+ const content = readFileSync(CONFIG_PATH, 'utf-8');
35
+ return JSON.parse(content);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Saves config to ~/.x402-config.json
43
+ */
44
+ export function saveConfig(config) {
45
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
46
+ }
47
+ /**
48
+ * Gets the active wallet's private key
49
+ * Returns null if no wallet is configured
50
+ */
51
+ export function getActiveWallet() {
52
+ // First check env var for backward compatibility
53
+ if (process.env.X402_PRIVATE_KEY) {
54
+ const type = detectWalletType(process.env.X402_PRIVATE_KEY);
55
+ return { type, privateKey: process.env.X402_PRIVATE_KEY };
56
+ }
57
+ // Then check config file
58
+ const config = loadConfig();
59
+ if (!config) {
60
+ return null;
61
+ }
62
+ const activeType = config.activeWallet;
63
+ const privateKey = config.wallets[activeType];
64
+ if (!privateKey) {
65
+ return null;
66
+ }
67
+ return { type: activeType, privateKey };
68
+ }
69
+ /**
70
+ * Validates required environment variables or config.
71
+ * Returns error message if no wallet configured, null if valid.
27
72
  */
28
73
  export function validateEnvironment() {
29
- if (!process.env.X402_PRIVATE_KEY) {
30
- return 'X402_PRIVATE_KEY not set. Run /x402storage:setup to configure.';
74
+ const wallet = getActiveWallet();
75
+ if (!wallet) {
76
+ return 'No wallet configured. Run /x402storage:setup to configure.';
31
77
  }
32
78
  return null;
33
79
  }
34
80
  /**
35
- * Gets the private key from environment.
81
+ * Gets the private key from environment or config.
36
82
  * Should only be called after validateEnvironment() returns null.
37
83
  */
38
84
  export function getPrivateKey() {
39
- return process.env.X402_PRIVATE_KEY;
85
+ const wallet = getActiveWallet();
86
+ return wallet.privateKey;
40
87
  }
41
88
  /**
42
89
  * Gets wallet address and balance from the configured private key
43
90
  */
44
91
  export async function getWalletInfo() {
45
- const privateKey = getPrivateKey();
46
- const walletType = detectWalletType(privateKey);
47
- if (walletType === 'solana') {
48
- return getSolanaWalletInfo(privateKey);
92
+ const wallet = getActiveWallet();
93
+ if (!wallet) {
94
+ throw new Error('No wallet configured');
95
+ }
96
+ if (wallet.type === 'sol') {
97
+ const info = await getSolanaWalletInfo(wallet.privateKey);
98
+ return { ...info, type: 'sol', isActive: true };
49
99
  }
50
100
  else {
51
- return getEvmWalletInfo(privateKey);
101
+ const info = await getEvmWalletInfo(wallet.privateKey);
102
+ return { ...info, type: 'evm', isActive: true };
103
+ }
104
+ }
105
+ /**
106
+ * Gets info for all configured wallets
107
+ */
108
+ export async function getAllWalletInfo() {
109
+ const results = [];
110
+ // Check env var first
111
+ if (process.env.X402_PRIVATE_KEY) {
112
+ const type = detectWalletType(process.env.X402_PRIVATE_KEY);
113
+ if (type === 'sol') {
114
+ const info = await getSolanaWalletInfo(process.env.X402_PRIVATE_KEY);
115
+ results.push({ ...info, type: 'sol', isActive: true });
116
+ }
117
+ else {
118
+ const info = await getEvmWalletInfo(process.env.X402_PRIVATE_KEY);
119
+ results.push({ ...info, type: 'evm', isActive: true });
120
+ }
121
+ return results;
122
+ }
123
+ // Check config file
124
+ const config = loadConfig();
125
+ if (!config) {
126
+ return results;
127
+ }
128
+ // Get EVM wallet info if configured
129
+ if (config.wallets.evm) {
130
+ try {
131
+ const info = await getEvmWalletInfo(config.wallets.evm);
132
+ results.push({
133
+ ...info,
134
+ type: 'evm',
135
+ isActive: config.activeWallet === 'evm',
136
+ });
137
+ }
138
+ catch (error) {
139
+ results.push({
140
+ address: 'Error loading wallet',
141
+ balance: 'N/A',
142
+ chain: 'Base',
143
+ type: 'evm',
144
+ isActive: config.activeWallet === 'evm',
145
+ });
146
+ }
147
+ }
148
+ // Get SOL wallet info if configured
149
+ if (config.wallets.sol) {
150
+ try {
151
+ const info = await getSolanaWalletInfo(config.wallets.sol);
152
+ results.push({
153
+ ...info,
154
+ type: 'sol',
155
+ isActive: config.activeWallet === 'sol',
156
+ });
157
+ }
158
+ catch (error) {
159
+ results.push({
160
+ address: 'Error loading wallet',
161
+ balance: 'N/A',
162
+ chain: 'Solana',
163
+ type: 'sol',
164
+ isActive: config.activeWallet === 'sol',
165
+ });
166
+ }
167
+ }
168
+ return results;
169
+ }
170
+ /**
171
+ * Sets the active wallet type
172
+ * Returns error message if wallet type not configured, null on success
173
+ */
174
+ export function setActiveWallet(type) {
175
+ const config = loadConfig();
176
+ if (!config) {
177
+ return 'No config file found. Run /x402storage:setup first.';
178
+ }
179
+ if (!config.wallets[type]) {
180
+ return `No ${type === 'evm' ? 'EVM (Base)' : 'Solana'} wallet configured. Run /x402storage:setup to add one.`;
181
+ }
182
+ config.activeWallet = type;
183
+ saveConfig(config);
184
+ return null;
185
+ }
186
+ /**
187
+ * Checks if legacy wallet file exists and returns migration info
188
+ */
189
+ export function checkLegacyWallet() {
190
+ if (!existsSync(LEGACY_WALLET_PATH)) {
191
+ return { exists: false };
192
+ }
193
+ try {
194
+ const privateKey = readFileSync(LEGACY_WALLET_PATH, 'utf-8').trim();
195
+ const type = detectWalletType(privateKey);
196
+ return { exists: true, type, privateKey };
197
+ }
198
+ catch {
199
+ return { exists: false };
52
200
  }
53
201
  }
54
202
  /**
@@ -101,3 +249,9 @@ async function getEvmWalletInfo(privateKey) {
101
249
  chain: 'Base',
102
250
  };
103
251
  }
252
+ /**
253
+ * Gets the config file path (for CLI output)
254
+ */
255
+ export function getConfigPath() {
256
+ return CONFIG_PATH;
257
+ }
package/dist/index.js CHANGED
@@ -4,23 +4,65 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { z } from 'zod';
5
5
  import { resolve } from 'node:path';
6
6
  import { randomBytes } from 'node:crypto';
7
- import { writeFileSync } from 'node:fs';
8
7
  import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
9
  import { privateKeyToAccount } from 'viem/accounts';
10
- import { validateEnvironment, getPrivateKey, getWalletInfo } from './config.js';
10
+ import { Keypair } from '@solana/web3.js';
11
+ import { validateEnvironment, getPrivateKey, getAllWalletInfo, setActiveWallet, loadConfig, saveConfig, } from './config.js';
11
12
  import { uploadFile, FileNotFoundError, UploadError } from './upload.js';
12
- // Handle --generate-wallet flag before MCP server setup
13
- if (process.argv.includes('--generate-wallet')) {
13
+ const CONFIG_PATH = join(homedir(), '.x402-config.json');
14
+ // Handle CLI flags before MCP server setup
15
+ const args = process.argv.slice(2);
16
+ // --generate-wallet (legacy, generates EVM wallet)
17
+ if (args.includes('--generate-wallet') || args.includes('--generate-evm-wallet')) {
14
18
  const privateKey = `0x${randomBytes(32).toString('hex')}`;
15
- const walletPath = `${homedir()}/.x402-wallet`;
16
- writeFileSync(walletPath, privateKey, { mode: 0o600 });
17
19
  const account = privateKeyToAccount(privateKey);
20
+ // Add to config file
21
+ let config = loadConfig();
22
+ if (!config) {
23
+ config = { activeWallet: 'evm', wallets: {} };
24
+ }
25
+ config.wallets.evm = privateKey;
26
+ config.activeWallet = 'evm';
27
+ saveConfig(config);
18
28
  console.log(account.address);
19
29
  process.exit(0);
20
30
  }
31
+ // --generate-sol-wallet
32
+ if (args.includes('--generate-sol-wallet')) {
33
+ const keypair = Keypair.generate();
34
+ const privateKey = JSON.stringify(Array.from(keypair.secretKey));
35
+ const address = keypair.publicKey.toBase58();
36
+ // Add to config file
37
+ let config = loadConfig();
38
+ if (!config) {
39
+ config = { activeWallet: 'sol', wallets: {} };
40
+ }
41
+ config.wallets.sol = privateKey;
42
+ config.activeWallet = 'sol';
43
+ saveConfig(config);
44
+ console.log(address);
45
+ process.exit(0);
46
+ }
47
+ // --set-active-wallet <type>
48
+ const setActiveIdx = args.indexOf('--set-active-wallet');
49
+ if (setActiveIdx !== -1) {
50
+ const walletType = args[setActiveIdx + 1];
51
+ if (!walletType || (walletType !== 'evm' && walletType !== 'sol')) {
52
+ console.error('Error: --set-active-wallet requires "evm" or "sol" as argument');
53
+ process.exit(1);
54
+ }
55
+ const error = setActiveWallet(walletType);
56
+ if (error) {
57
+ console.error(`Error: ${error}`);
58
+ process.exit(1);
59
+ }
60
+ console.log(`Active wallet set to ${walletType === 'evm' ? 'EVM (Base)' : 'Solana'}`);
61
+ process.exit(0);
62
+ }
21
63
  // Create MCP server
22
64
  const server = new McpServer({
23
- name: 'x402store',
65
+ name: 'x402storage',
24
66
  version: '1.0.0',
25
67
  });
26
68
  // Register store_file tool
@@ -83,21 +125,34 @@ server.tool('store_file', 'Store a file permanently on IPFS via x402.storage. Re
83
125
  };
84
126
  }
85
127
  });
86
- // Register wallet tool
128
+ // Register wallet tool - shows ALL configured wallets
87
129
  server.tool('wallet', 'Show your x402.storage wallet address and balance', {}, async () => {
88
- const envError = validateEnvironment();
89
- if (envError) {
90
- return {
91
- content: [{ type: 'text', text: `Error: ${envError}` }],
92
- };
93
- }
94
130
  try {
95
- const info = await getWalletInfo();
131
+ const wallets = await getAllWalletInfo();
132
+ if (wallets.length === 0) {
133
+ return {
134
+ content: [
135
+ {
136
+ type: 'text',
137
+ text: 'No wallets configured. Run /x402storage:setup to configure.',
138
+ },
139
+ ],
140
+ };
141
+ }
142
+ const lines = [];
143
+ for (const wallet of wallets) {
144
+ const activeMarker = wallet.isActive ? ' [ACTIVE]' : '';
145
+ const chainLabel = wallet.type === 'evm' ? 'EVM (Base)' : 'Solana';
146
+ lines.push(`${chainLabel}${activeMarker}`);
147
+ lines.push(` Address: ${wallet.address}`);
148
+ lines.push(` Balance: ${wallet.balance}`);
149
+ lines.push('');
150
+ }
96
151
  return {
97
152
  content: [
98
153
  {
99
154
  type: 'text',
100
- text: `Address: ${info.address}\nBalance: ${info.balance}`,
155
+ text: lines.join('\n').trim(),
101
156
  },
102
157
  ],
103
158
  };
@@ -109,13 +164,35 @@ server.tool('wallet', 'Show your x402.storage wallet address and balance', {}, a
109
164
  };
110
165
  }
111
166
  });
167
+ // Register set_active_wallet tool
168
+ server.tool('set_active_wallet', 'Set which wallet (evm or sol) to use for payments. Note: Claude Code restart required for changes to take effect.', {
169
+ wallet_type: z
170
+ .enum(['evm', 'sol'])
171
+ .describe('Wallet type to set as active: "evm" for Base/USDC or "sol" for Solana'),
172
+ }, async ({ wallet_type }) => {
173
+ const error = setActiveWallet(wallet_type);
174
+ if (error) {
175
+ return {
176
+ content: [{ type: 'text', text: `Error: ${error}` }],
177
+ };
178
+ }
179
+ const chainLabel = wallet_type === 'evm' ? 'EVM (Base)' : 'Solana';
180
+ return {
181
+ content: [
182
+ {
183
+ type: 'text',
184
+ text: `Active wallet set to ${chainLabel}.\n\nNote: Restart Claude Code for the change to take effect.`,
185
+ },
186
+ ],
187
+ };
188
+ });
112
189
  // Run server with stdio transport
113
190
  async function main() {
114
191
  const transport = new StdioServerTransport();
115
192
  await server.connect(transport);
116
193
  // Note: MCP servers must not write to stdout (breaks JSON-RPC)
117
194
  // Use console.error for any debugging
118
- console.error('x402store MCP server running on stdio');
195
+ console.error('x402storage MCP server running on stdio');
119
196
  }
120
197
  main().catch((error) => {
121
198
  console.error('Fatal error:', error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x402storage/mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "MCP server for x402.storage file uploads",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,6 +9,7 @@
9
9
  "url": "https://github.com/rawgroundbeef/x402.storage"
10
10
  },
11
11
  "bin": {
12
+ "mcp": "./dist/index.js",
12
13
  "x402-mcp": "./dist/index.js"
13
14
  },
14
15
  "files": [