@nullpay/mcp 1.0.0 → 1.0.3
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 +186 -19
- package/dist/aleo.d.ts +16 -0
- package/dist/aleo.js +125 -5
- package/dist/backend-client.d.ts +1 -2
- package/dist/backend-client.js +2 -14
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +35 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +75 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +36 -85
- package/dist/service.js +78 -9
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +140 -0
- package/dist/utils/crypto.d.ts +3 -3
- package/dist/utils/crypto.js +66 -66
- package/dist/utils/env.d.ts +6 -6
- package/dist/utils/env.js +15 -15
- package/dist/utils/esm.d.ts +1 -1
- package/dist/utils/esm.js +7 -7
- package/dist/utils/formatters.d.ts +14 -14
- package/dist/utils/formatters.js +132 -132
- package/package.json +32 -37
package/dist/server.js
CHANGED
|
@@ -1,59 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
3
|
+
exports.startServer = startServer;
|
|
8
4
|
const backend_client_1 = require("./backend-client");
|
|
5
|
+
const env_1 = require("./env");
|
|
9
6
|
const protocol_1 = require("./protocol");
|
|
10
7
|
const session_store_1 = require("./session-store");
|
|
11
8
|
const service_1 = require("./service");
|
|
12
|
-
function parseDotEnv(content) {
|
|
13
|
-
const parsed = {};
|
|
14
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
15
|
-
const line = rawLine.trim();
|
|
16
|
-
if (!line || line.startsWith('#')) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
|
|
20
|
-
const separatorIndex = normalized.indexOf('=');
|
|
21
|
-
if (separatorIndex <= 0) {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const key = normalized.slice(0, separatorIndex).trim();
|
|
25
|
-
let value = normalized.slice(separatorIndex + 1).trim();
|
|
26
|
-
if (!key) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
30
|
-
value = value.slice(1, -1);
|
|
31
|
-
}
|
|
32
|
-
parsed[key] = value;
|
|
33
|
-
}
|
|
34
|
-
return parsed;
|
|
35
|
-
}
|
|
36
|
-
function loadEnvFiles() {
|
|
37
|
-
const packageRoot = path_1.default.resolve(__dirname, '..');
|
|
38
|
-
const repoRoot = path_1.default.resolve(packageRoot, '..', '..');
|
|
39
|
-
const candidates = [
|
|
40
|
-
path_1.default.resolve(process.cwd(), '.env'),
|
|
41
|
-
path_1.default.resolve(packageRoot, '.env'),
|
|
42
|
-
path_1.default.resolve(repoRoot, '.env'),
|
|
43
|
-
path_1.default.resolve(repoRoot, 'backend', '.env'),
|
|
44
|
-
];
|
|
45
|
-
for (const filePath of candidates) {
|
|
46
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const values = parseDotEnv(fs_1.default.readFileSync(filePath, 'utf8'));
|
|
50
|
-
for (const [key, value] of Object.entries(values)) {
|
|
51
|
-
if (!process.env[key]) {
|
|
52
|
-
process.env[key] = value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
9
|
function shieldStdoutForMcp() {
|
|
58
10
|
const protocolWrite = process.stdout.write.bind(process.stdout);
|
|
59
11
|
globalThis.__nullpayMcpStdoutWrite = (chunk) => protocolWrite(chunk);
|
|
@@ -73,39 +25,38 @@ function shieldStdoutForMcp() {
|
|
|
73
25
|
return true;
|
|
74
26
|
});
|
|
75
27
|
}
|
|
76
|
-
|
|
77
|
-
shieldStdoutForMcp();
|
|
78
|
-
const backendBaseUrl =
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
28
|
+
function startServer() {
|
|
29
|
+
shieldStdoutForMcp();
|
|
30
|
+
const { backendBaseUrl, publicBaseUrl } = (0, env_1.getRuntimeConfig)();
|
|
31
|
+
const backend = new backend_client_1.NullPayBackendClient(backendBaseUrl);
|
|
32
|
+
const sessions = new session_store_1.SessionStore();
|
|
33
|
+
const service = new service_1.NullPayMcpService(backend, sessions, publicBaseUrl);
|
|
34
|
+
const server = new protocol_1.StdioJsonRpcServer(async (request) => {
|
|
35
|
+
if (request.method === 'initialize') {
|
|
36
|
+
return {
|
|
37
|
+
protocolVersion: String(request.params?.protocolVersion || '2025-11-25'),
|
|
38
|
+
capabilities: {
|
|
39
|
+
tools: {}
|
|
40
|
+
},
|
|
41
|
+
serverInfo: {
|
|
42
|
+
name: 'nullpay-mcp',
|
|
43
|
+
version: '0.2.0'
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (request.method === 'notifications/initialized') {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
if (request.method === 'tools/list') {
|
|
51
|
+
return { tools: service.listTools() };
|
|
52
|
+
}
|
|
53
|
+
if (request.method === 'tools/call') {
|
|
54
|
+
const name = String(request.params?.name || '');
|
|
55
|
+
const args = (request.params?.arguments || {});
|
|
56
|
+
return await service.callTool(name, args);
|
|
57
|
+
}
|
|
98
58
|
return {};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (request.method === 'tools/call') {
|
|
104
|
-
const name = String(request.params?.name || '');
|
|
105
|
-
const args = (request.params?.arguments || {});
|
|
106
|
-
return await service.callTool(name, args);
|
|
107
|
-
}
|
|
108
|
-
return {};
|
|
109
|
-
});
|
|
110
|
-
console.error('nullpay mcp starting');
|
|
111
|
-
server.start();
|
|
59
|
+
});
|
|
60
|
+
console.error(`nullpay mcp starting against ${backendBaseUrl}`);
|
|
61
|
+
server.start();
|
|
62
|
+
}
|
package/dist/service.js
CHANGED
|
@@ -142,7 +142,7 @@ class NullPayMcpService {
|
|
|
142
142
|
return [
|
|
143
143
|
{
|
|
144
144
|
name: 'login',
|
|
145
|
-
description: 'Login to NullPay, validate password, create burner wallet, or switch active wallet. If NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD are configured, call this tool with empty arguments and do not ask the user to share secrets in chat. The MCP server can also read NULLPAY_MAIN_PRIVATE_KEY from env without exposing it to the model.',
|
|
145
|
+
description: 'Login to NullPay, validate password, create burner wallet, recover a backed-up password or burner wallet from on-chain records, or switch active wallet. If NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD are configured, call this tool with empty arguments and do not ask the user to share secrets in chat. The MCP server can also read NULLPAY_MAIN_PRIVATE_KEY from env without exposing it to the model.',
|
|
146
146
|
inputSchema: {
|
|
147
147
|
type: 'object',
|
|
148
148
|
properties: {
|
|
@@ -222,21 +222,70 @@ class NullPayMcpService {
|
|
|
222
222
|
async login(args) {
|
|
223
223
|
const envMain = getMainWalletEnv();
|
|
224
224
|
const address = (args.address || envMain.address || '').trim();
|
|
225
|
-
|
|
225
|
+
let password = args.password || envMain.password || '';
|
|
226
226
|
const mainPrivateKey = args.main_private_key || envMain.privateKey || null;
|
|
227
|
-
if (!address
|
|
228
|
-
throw new Error('Address
|
|
227
|
+
if (!address) {
|
|
228
|
+
throw new Error('Address is required. You can pass it directly or set NULLPAY_MAIN_ADDRESS in env.');
|
|
229
229
|
}
|
|
230
230
|
const addressHash = (0, crypto_1.hashAddress)(address);
|
|
231
231
|
const existingProfile = await this.backend.getUserProfile(addressHash);
|
|
232
232
|
let encryptedMainAddress = existingProfile?.main_address || null;
|
|
233
|
+
let recoveredBackupSource = null;
|
|
234
|
+
let recoveredBurnerAddress = null;
|
|
235
|
+
let recoveredEncryptedBurnerKey = null;
|
|
236
|
+
let usedRecoveredPassword = false;
|
|
237
|
+
let restoredBurnerFromChain = false;
|
|
238
|
+
const attemptRecovery = async () => {
|
|
239
|
+
if (!mainPrivateKey) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const recovered = await (0, aleo_1.recoverOnChainWalletBackup)(mainPrivateKey, address);
|
|
243
|
+
if (!recovered?.password) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
password = recovered.password;
|
|
247
|
+
recoveredBackupSource = recovered.source;
|
|
248
|
+
recoveredBurnerAddress = recovered.burnerAddress || null;
|
|
249
|
+
recoveredEncryptedBurnerKey = recovered.encryptedBurnerKey || null;
|
|
250
|
+
usedRecoveredPassword = true;
|
|
251
|
+
return true;
|
|
252
|
+
};
|
|
233
253
|
if (encryptedMainAddress) {
|
|
234
|
-
|
|
254
|
+
if (!password) {
|
|
255
|
+
const recovered = await attemptRecovery();
|
|
256
|
+
if (!recovered) {
|
|
257
|
+
throw new Error('Password is required for this NullPay account. Set NULLPAY_MAIN_PASSWORD, pass password directly, or provide NULLPAY_MAIN_PRIVATE_KEY so the MCP can recover a backed-up password from on-chain records.');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
let decrypted;
|
|
261
|
+
try {
|
|
262
|
+
decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
const recovered = await attemptRecovery();
|
|
266
|
+
if (!recovered) {
|
|
267
|
+
throw new Error('Password is incorrect for this NullPay account, and no recoverable on-chain password backup was found for the provided main private key.');
|
|
268
|
+
}
|
|
269
|
+
decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
|
|
270
|
+
}
|
|
235
271
|
if (decrypted !== address) {
|
|
236
|
-
|
|
272
|
+
const recovered = await attemptRecovery();
|
|
273
|
+
if (!recovered) {
|
|
274
|
+
throw new Error('Password is incorrect for this NullPay account.');
|
|
275
|
+
}
|
|
276
|
+
const recoveredAddress = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
|
|
277
|
+
if (recoveredAddress !== address) {
|
|
278
|
+
throw new Error('Recovered password does not match the provided address.');
|
|
279
|
+
}
|
|
237
280
|
}
|
|
238
281
|
}
|
|
239
282
|
else {
|
|
283
|
+
if (!password) {
|
|
284
|
+
const recovered = await attemptRecovery();
|
|
285
|
+
if (!recovered) {
|
|
286
|
+
throw new Error('Password is required to create a new NullPay account unless the MCP can recover it from on-chain backup records using NULLPAY_MAIN_PRIVATE_KEY.');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
240
289
|
encryptedMainAddress = await (0, crypto_1.encryptWithPassword)(address, password);
|
|
241
290
|
}
|
|
242
291
|
if (!encryptedMainAddress) {
|
|
@@ -246,7 +295,18 @@ class NullPayMcpService {
|
|
|
246
295
|
let encryptedBurnerKey = existingProfile?.encrypted_burner_key || null;
|
|
247
296
|
let burnerAddress = null;
|
|
248
297
|
if (encryptedBurnerAddress) {
|
|
249
|
-
|
|
298
|
+
try {
|
|
299
|
+
burnerAddress = await (0, crypto_1.decryptWithPassword)(encryptedBurnerAddress, password);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
burnerAddress = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if ((!encryptedBurnerAddress || !encryptedBurnerKey || !burnerAddress) && recoveredBurnerAddress && recoveredEncryptedBurnerKey) {
|
|
306
|
+
burnerAddress = recoveredBurnerAddress;
|
|
307
|
+
encryptedBurnerAddress = await (0, crypto_1.encryptWithPassword)(recoveredBurnerAddress, password);
|
|
308
|
+
encryptedBurnerKey = recoveredEncryptedBurnerKey;
|
|
309
|
+
restoredBurnerFromChain = true;
|
|
250
310
|
}
|
|
251
311
|
if (args.create_burner_wallet && !encryptedBurnerKey) {
|
|
252
312
|
const { PrivateKey } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
|
|
@@ -282,11 +342,17 @@ class NullPayMcpService {
|
|
|
282
342
|
});
|
|
283
343
|
const usedEnvCredentials = Boolean(envMain.address && envMain.password && !args.address && !args.password);
|
|
284
344
|
const lines = [
|
|
285
|
-
usedEnvCredentials ? 'Used main-wallet address and password from MCP env.' :
|
|
345
|
+
usedEnvCredentials ? 'Used main-wallet address and password from MCP env.' : 'Logged in as ' + address + '.',
|
|
286
346
|
encryptedBurnerKey
|
|
287
|
-
?
|
|
347
|
+
? 'Active wallet: ' + preferredWallet + '. Burner wallet is available' + (burnerAddress ? ' at ' + burnerAddress : '') + '.'
|
|
288
348
|
: 'Active wallet: main. No burner wallet is stored yet.',
|
|
289
349
|
];
|
|
350
|
+
if (usedRecoveredPassword) {
|
|
351
|
+
lines.push('Recovered your NullPay password from on-chain backup records using the main wallet private key (' + (recoveredBackupSource === 'full_burner' ? 'full burner backup' : 'password backup') + ').');
|
|
352
|
+
}
|
|
353
|
+
if (restoredBurnerFromChain) {
|
|
354
|
+
lines.push('Recovered your backed-up burner wallet from on-chain records and restored it into the MCP session.');
|
|
355
|
+
}
|
|
290
356
|
if (mainPrivateKey) {
|
|
291
357
|
lines.push('Main wallet private key is available for record-backed amount lookup and main-wallet payments. Active wallet is set to main by default, and you can switch to burner anytime by logging in again with wallet_preference set to burner. Invoice lookup will prefer the main wallet records even when you pay from burner.');
|
|
292
358
|
}
|
|
@@ -306,6 +372,9 @@ class NullPayMcpService {
|
|
|
306
372
|
has_main_private_key: Boolean(mainPrivateKey),
|
|
307
373
|
main_private_key_from_env: Boolean(envMain.privateKey),
|
|
308
374
|
used_env_credentials: usedEnvCredentials,
|
|
375
|
+
used_recovered_password: usedRecoveredPassword,
|
|
376
|
+
recovery_source: recoveredBackupSource,
|
|
377
|
+
restored_burner_from_chain: restoredBurnerFromChain,
|
|
309
378
|
},
|
|
310
379
|
};
|
|
311
380
|
}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSetupWizard(): Promise<void>;
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runSetupWizard = runSetupWizard;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const promises_1 = __importDefault(require("readline/promises"));
|
|
11
|
+
const process_1 = require("process");
|
|
12
|
+
function getClaudeCodeConfigPath() {
|
|
13
|
+
return path_1.default.join(os_1.default.homedir(), '.claude.json');
|
|
14
|
+
}
|
|
15
|
+
function getClaudeDesktopConfigPath() {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
const localAppData = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
|
|
18
|
+
const packagedClaudePath = path_1.default.join(localAppData, 'Packages', 'Claude_pzs8sxrjxfjjc', 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
19
|
+
if (fs_1.default.existsSync(packagedClaudePath) || fs_1.default.existsSync(path_1.default.dirname(packagedClaudePath))) {
|
|
20
|
+
return packagedClaudePath;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (process.platform === 'win32') {
|
|
24
|
+
const appData = process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming');
|
|
25
|
+
return path_1.default.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
26
|
+
}
|
|
27
|
+
if (process.platform === 'darwin') {
|
|
28
|
+
return path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
29
|
+
}
|
|
30
|
+
return path_1.default.join(os_1.default.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
31
|
+
}
|
|
32
|
+
function buildServerEntry(answers) {
|
|
33
|
+
const env = {
|
|
34
|
+
NULLPAY_MAIN_ADDRESS: answers.address,
|
|
35
|
+
NULLPAY_MAIN_PRIVATE_KEY: answers.privateKey,
|
|
36
|
+
NULLPAY_MAIN_PASSWORD: answers.password,
|
|
37
|
+
};
|
|
38
|
+
if (process.platform === 'win32') {
|
|
39
|
+
return {
|
|
40
|
+
command: 'cmd',
|
|
41
|
+
args: ['/c', 'npx', '-y', '@nullpay/mcp', 'server'],
|
|
42
|
+
env,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
command: 'npx',
|
|
47
|
+
args: ['-y', '@nullpay/mcp', 'server'],
|
|
48
|
+
env,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function readJsonConfig(configPath) {
|
|
52
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
const raw = fs_1.default.readFileSync(configPath, 'utf8').trim();
|
|
56
|
+
if (!raw) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
61
|
+
throw new Error(`Config at ${configPath} is not a JSON object.`);
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
function writeMcpConfig(configPath, serverName, entry) {
|
|
66
|
+
const current = readJsonConfig(configPath);
|
|
67
|
+
const currentMcpServers = current.mcpServers && typeof current.mcpServers === 'object' && !Array.isArray(current.mcpServers)
|
|
68
|
+
? current.mcpServers
|
|
69
|
+
: {};
|
|
70
|
+
const next = {
|
|
71
|
+
...current,
|
|
72
|
+
mcpServers: {
|
|
73
|
+
...currentMcpServers,
|
|
74
|
+
[serverName]: entry,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
|
|
78
|
+
fs_1.default.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
async function askChoice(rl) {
|
|
81
|
+
process_1.stdout.write('Where do you want to install NullPay MCP?\n');
|
|
82
|
+
process_1.stdout.write('1. Claude Code\n');
|
|
83
|
+
process_1.stdout.write('2. Claude Desktop\n');
|
|
84
|
+
process_1.stdout.write('3. Cancel\n');
|
|
85
|
+
while (true) {
|
|
86
|
+
const answer = (await rl.question('Choose 1, 2, or 3: ')).trim();
|
|
87
|
+
if (answer === '1')
|
|
88
|
+
return 'claude-code';
|
|
89
|
+
if (answer === '2')
|
|
90
|
+
return 'claude-desktop';
|
|
91
|
+
if (answer === '3')
|
|
92
|
+
return null;
|
|
93
|
+
process_1.stdout.write('Please enter 1, 2, or 3.\n');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function askRequired(rl, label) {
|
|
97
|
+
while (true) {
|
|
98
|
+
const answer = (await rl.question(`${label}: `)).trim();
|
|
99
|
+
if (answer) {
|
|
100
|
+
return answer;
|
|
101
|
+
}
|
|
102
|
+
process_1.stdout.write(`${label} is required.\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function collectAnswers(rl) {
|
|
106
|
+
process_1.stdout.write('\nNullPay will configure Claude automatically. You only need to provide your wallet credentials here.\n\n');
|
|
107
|
+
const address = await askRequired(rl, 'Main wallet address');
|
|
108
|
+
const privateKey = await askRequired(rl, 'Main wallet private key');
|
|
109
|
+
const password = await askRequired(rl, 'NullPay password');
|
|
110
|
+
return {
|
|
111
|
+
address,
|
|
112
|
+
privateKey,
|
|
113
|
+
password,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function runSetupWizard() {
|
|
117
|
+
const rl = promises_1.default.createInterface({ input: process_1.stdin, output: process_1.stdout });
|
|
118
|
+
try {
|
|
119
|
+
process_1.stdout.write('NullPay MCP setup\n\n');
|
|
120
|
+
const choice = await askChoice(rl);
|
|
121
|
+
if (!choice) {
|
|
122
|
+
process_1.stdout.write('Setup cancelled.\n');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const answers = await collectAnswers(rl);
|
|
126
|
+
const entry = buildServerEntry(answers);
|
|
127
|
+
const configPath = choice === 'claude-code' ? getClaudeCodeConfigPath() : getClaudeDesktopConfigPath();
|
|
128
|
+
writeMcpConfig(configPath, 'nullpay', entry);
|
|
129
|
+
process_1.stdout.write(`\nNullPay MCP was added to ${configPath}\n`);
|
|
130
|
+
if (choice === 'claude-code') {
|
|
131
|
+
process_1.stdout.write('Next step: restart Claude Code or run `claude mcp list` to confirm the server is registered.\n');
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
process_1.stdout.write('Next step: fully restart Claude Desktop so it reloads the new MCP config.\n');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
rl.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
package/dist/utils/crypto.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare function hashAddress(address: string): string;
|
|
2
|
-
export declare function encryptWithPassword(text: string, password: string): Promise<string>;
|
|
3
|
-
export declare function decryptWithPassword(payload: string, password: string): Promise<string>;
|
|
1
|
+
export declare function hashAddress(address: string): string;
|
|
2
|
+
export declare function encryptWithPassword(text: string, password: string): Promise<string>;
|
|
3
|
+
export declare function decryptWithPassword(payload: string, password: string): Promise<string>;
|
package/dist/utils/crypto.js
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.hashAddress = hashAddress;
|
|
7
|
-
exports.encryptWithPassword = encryptWithPassword;
|
|
8
|
-
exports.decryptWithPassword = decryptWithPassword;
|
|
9
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
-
const ITERATIONS = 100000;
|
|
11
|
-
const KEY_LENGTH = 32;
|
|
12
|
-
const DIGEST = 'sha256';
|
|
13
|
-
function toBase64(buffer) {
|
|
14
|
-
return Buffer.from(buffer).toString('base64');
|
|
15
|
-
}
|
|
16
|
-
function fromBase64(value) {
|
|
17
|
-
return Buffer.from(value, 'base64');
|
|
18
|
-
}
|
|
19
|
-
function hashAddress(address) {
|
|
20
|
-
return crypto_1.default.createHash('sha256').update(address).digest('hex');
|
|
21
|
-
}
|
|
22
|
-
async function encryptWithPassword(text, password) {
|
|
23
|
-
const salt = crypto_1.default.randomBytes(16);
|
|
24
|
-
const iv = crypto_1.default.randomBytes(12);
|
|
25
|
-
const key = await new Promise((resolve, reject) => {
|
|
26
|
-
crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
|
|
27
|
-
if (error)
|
|
28
|
-
reject(error);
|
|
29
|
-
else
|
|
30
|
-
resolve(derivedKey);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
|
|
34
|
-
const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
35
|
-
const tag = cipher.getAuthTag();
|
|
36
|
-
return `${toBase64(salt)}:${toBase64(iv)}:${toBase64(Buffer.concat([ciphertext, tag]))}`;
|
|
37
|
-
}
|
|
38
|
-
async function decryptWithPassword(payload, password) {
|
|
39
|
-
const parts = payload.split(':');
|
|
40
|
-
if (parts.length !== 3) {
|
|
41
|
-
throw new Error('Invalid encrypted payload format');
|
|
42
|
-
}
|
|
43
|
-
const [saltPart, ivPart, cipherPart] = parts;
|
|
44
|
-
const salt = fromBase64(saltPart);
|
|
45
|
-
const iv = fromBase64(ivPart);
|
|
46
|
-
const cipherWithTag = fromBase64(cipherPart);
|
|
47
|
-
const ciphertext = cipherWithTag.subarray(0, cipherWithTag.length - 16);
|
|
48
|
-
const tag = cipherWithTag.subarray(cipherWithTag.length - 16);
|
|
49
|
-
const key = await new Promise((resolve, reject) => {
|
|
50
|
-
crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
|
|
51
|
-
if (error)
|
|
52
|
-
reject(error);
|
|
53
|
-
else
|
|
54
|
-
resolve(derivedKey);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
try {
|
|
58
|
-
const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
|
|
59
|
-
decipher.setAuthTag(tag);
|
|
60
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
61
|
-
return plaintext.toString('utf8');
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
throw new Error('Incorrect password or corrupted data');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.hashAddress = hashAddress;
|
|
7
|
+
exports.encryptWithPassword = encryptWithPassword;
|
|
8
|
+
exports.decryptWithPassword = decryptWithPassword;
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
const ITERATIONS = 100000;
|
|
11
|
+
const KEY_LENGTH = 32;
|
|
12
|
+
const DIGEST = 'sha256';
|
|
13
|
+
function toBase64(buffer) {
|
|
14
|
+
return Buffer.from(buffer).toString('base64');
|
|
15
|
+
}
|
|
16
|
+
function fromBase64(value) {
|
|
17
|
+
return Buffer.from(value, 'base64');
|
|
18
|
+
}
|
|
19
|
+
function hashAddress(address) {
|
|
20
|
+
return crypto_1.default.createHash('sha256').update(address).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
async function encryptWithPassword(text, password) {
|
|
23
|
+
const salt = crypto_1.default.randomBytes(16);
|
|
24
|
+
const iv = crypto_1.default.randomBytes(12);
|
|
25
|
+
const key = await new Promise((resolve, reject) => {
|
|
26
|
+
crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
|
|
27
|
+
if (error)
|
|
28
|
+
reject(error);
|
|
29
|
+
else
|
|
30
|
+
resolve(derivedKey);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
|
|
34
|
+
const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
35
|
+
const tag = cipher.getAuthTag();
|
|
36
|
+
return `${toBase64(salt)}:${toBase64(iv)}:${toBase64(Buffer.concat([ciphertext, tag]))}`;
|
|
37
|
+
}
|
|
38
|
+
async function decryptWithPassword(payload, password) {
|
|
39
|
+
const parts = payload.split(':');
|
|
40
|
+
if (parts.length !== 3) {
|
|
41
|
+
throw new Error('Invalid encrypted payload format');
|
|
42
|
+
}
|
|
43
|
+
const [saltPart, ivPart, cipherPart] = parts;
|
|
44
|
+
const salt = fromBase64(saltPart);
|
|
45
|
+
const iv = fromBase64(ivPart);
|
|
46
|
+
const cipherWithTag = fromBase64(cipherPart);
|
|
47
|
+
const ciphertext = cipherWithTag.subarray(0, cipherWithTag.length - 16);
|
|
48
|
+
const tag = cipherWithTag.subarray(cipherWithTag.length - 16);
|
|
49
|
+
const key = await new Promise((resolve, reject) => {
|
|
50
|
+
crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
|
|
51
|
+
if (error)
|
|
52
|
+
reject(error);
|
|
53
|
+
else
|
|
54
|
+
resolve(derivedKey);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
|
|
59
|
+
decipher.setAuthTag(tag);
|
|
60
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
61
|
+
return plaintext.toString('utf8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new Error('Incorrect password or corrupted data');
|
|
65
|
+
}
|
|
66
|
+
}
|
package/dist/utils/env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare function readEnvTrimmed(name: string): string | undefined;
|
|
2
|
-
export declare function getMainWalletEnv(): {
|
|
3
|
-
address: string | undefined;
|
|
4
|
-
password: string | undefined;
|
|
5
|
-
privateKey: string | undefined;
|
|
6
|
-
};
|
|
1
|
+
export declare function readEnvTrimmed(name: string): string | undefined;
|
|
2
|
+
export declare function getMainWalletEnv(): {
|
|
3
|
+
address: string | undefined;
|
|
4
|
+
password: string | undefined;
|
|
5
|
+
privateKey: string | undefined;
|
|
6
|
+
};
|
package/dist/utils/env.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readEnvTrimmed = readEnvTrimmed;
|
|
4
|
-
exports.getMainWalletEnv = getMainWalletEnv;
|
|
5
|
-
function readEnvTrimmed(name) {
|
|
6
|
-
const value = process.env[name]?.trim();
|
|
7
|
-
return value ? value : undefined;
|
|
8
|
-
}
|
|
9
|
-
function getMainWalletEnv() {
|
|
10
|
-
return {
|
|
11
|
-
address: readEnvTrimmed('NULLPAY_MAIN_ADDRESS'),
|
|
12
|
-
password: readEnvTrimmed('NULLPAY_MAIN_PASSWORD'),
|
|
13
|
-
privateKey: readEnvTrimmed('NULLPAY_MAIN_PRIVATE_KEY') || readEnvTrimmed('NULLPAY_MAIN_PVT_KEY')
|
|
14
|
-
};
|
|
15
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readEnvTrimmed = readEnvTrimmed;
|
|
4
|
+
exports.getMainWalletEnv = getMainWalletEnv;
|
|
5
|
+
function readEnvTrimmed(name) {
|
|
6
|
+
const value = process.env[name]?.trim();
|
|
7
|
+
return value ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
function getMainWalletEnv() {
|
|
10
|
+
return {
|
|
11
|
+
address: readEnvTrimmed('NULLPAY_MAIN_ADDRESS'),
|
|
12
|
+
password: readEnvTrimmed('NULLPAY_MAIN_PASSWORD'),
|
|
13
|
+
privateKey: readEnvTrimmed('NULLPAY_MAIN_PRIVATE_KEY') || readEnvTrimmed('NULLPAY_MAIN_PVT_KEY')
|
|
14
|
+
};
|
|
15
|
+
}
|
package/dist/utils/esm.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function dynamicImport<T = any>(specifier: string): Promise<T>;
|
|
1
|
+
export declare function dynamicImport<T = any>(specifier: string): Promise<T>;
|
package/dist/utils/esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dynamicImport = dynamicImport;
|
|
4
|
-
async function dynamicImport(specifier) {
|
|
5
|
-
const importer = new Function('s', 'return import(s);');
|
|
6
|
-
return await importer(specifier);
|
|
7
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dynamicImport = dynamicImport;
|
|
4
|
+
async function dynamicImport(specifier) {
|
|
5
|
+
const importer = new Function('s', 'return import(s);');
|
|
6
|
+
return await importer(specifier);
|
|
7
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Currency, InvoiceRecord, InvoiceType } from '../types';
|
|
2
|
-
export declare function normalizeCurrency(value?: string): Currency;
|
|
3
|
-
export declare function normalizePaymentCurrency(value?: string): Exclude<Currency, 'ANY'> | undefined;
|
|
4
|
-
export declare function normalizeInvoiceType(value?: string): InvoiceType;
|
|
5
|
-
export declare function tokenTypeLabel(tokenType?: number | null): string;
|
|
6
|
-
export declare function invoiceTypeLabel(invoiceType?: number | null): string;
|
|
7
|
-
export declare function currencyToTokenType(currency?: Currency | null): number;
|
|
8
|
-
export declare function linkTokenToCurrency(token?: string | null): Currency | undefined;
|
|
9
|
-
export declare function linkTypeToInvoiceType(type?: string | null): InvoiceType;
|
|
10
|
-
export declare function parseAmount(value?: string | number | null): number | undefined;
|
|
11
|
-
export declare function shouldMarkInvoiceSettled(invoiceType?: number | null): boolean;
|
|
12
|
-
export declare function formatInvoiceSummary(invoice: InvoiceRecord): string;
|
|
13
|
-
export declare function getAmountSource(invoice: InvoiceRecord): 'record' | 'database' | 'missing';
|
|
14
|
-
export declare function buildAmountLookupHint(invoice: InvoiceRecord, hasInvoiceLookupKey: boolean): string;
|
|
1
|
+
import { Currency, InvoiceRecord, InvoiceType } from '../types';
|
|
2
|
+
export declare function normalizeCurrency(value?: string): Currency;
|
|
3
|
+
export declare function normalizePaymentCurrency(value?: string): Exclude<Currency, 'ANY'> | undefined;
|
|
4
|
+
export declare function normalizeInvoiceType(value?: string): InvoiceType;
|
|
5
|
+
export declare function tokenTypeLabel(tokenType?: number | null): string;
|
|
6
|
+
export declare function invoiceTypeLabel(invoiceType?: number | null): string;
|
|
7
|
+
export declare function currencyToTokenType(currency?: Currency | null): number;
|
|
8
|
+
export declare function linkTokenToCurrency(token?: string | null): Currency | undefined;
|
|
9
|
+
export declare function linkTypeToInvoiceType(type?: string | null): InvoiceType;
|
|
10
|
+
export declare function parseAmount(value?: string | number | null): number | undefined;
|
|
11
|
+
export declare function shouldMarkInvoiceSettled(invoiceType?: number | null): boolean;
|
|
12
|
+
export declare function formatInvoiceSummary(invoice: InvoiceRecord): string;
|
|
13
|
+
export declare function getAmountSource(invoice: InvoiceRecord): 'record' | 'database' | 'missing';
|
|
14
|
+
export declare function buildAmountLookupHint(invoice: InvoiceRecord, hasInvoiceLookupKey: boolean): string;
|