@nullpay/mcp 1.0.1 → 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 CHANGED
@@ -1,60 +1,216 @@
1
- # NullPay MCP
1
+ # @nullpay/mcp
2
2
 
3
- NullPay MCP installs a local MCP server for Claude and lets users connect with only their wallet credentials.
3
+ NullPay MCP is a local Model Context Protocol server for Claude that lets users create invoices, pay invoices, and inspect transactions through chat.
4
4
 
5
- ## Install
5
+ The package is designed so the user only needs to provide their own wallet credentials:
6
+
7
+ - `NULLPAY_MAIN_ADDRESS`
8
+ - `NULLPAY_MAIN_PRIVATE_KEY`
9
+ - `NULLPAY_MAIN_PASSWORD`
10
+
11
+ NullPay-owned infrastructure stays inside the package or on the backend:
12
+
13
+ - default backend API: `https://nullpay-backend-ib5q4.ondigitalocean.app/api`
14
+ - default public app URL: `https://nullpay.app`
15
+ - relayer private key: backend only
16
+
17
+ ## How It Works
18
+
19
+ 1. The user runs the setup wizard.
20
+ 2. The wizard asks whether to install into Claude Code or Claude Desktop.
21
+ 3. The wizard asks for the user's address, private key, and password.
22
+ 4. The wizard writes the MCP config file automatically on the user's machine.
23
+ 5. Claude starts the local NullPay MCP server with `@nullpay/mcp server`.
24
+ 6. The MCP server talks to the NullPay backend and keeps the user's private-key operations local.
25
+
26
+ ## Install For End Users
27
+
28
+ Run the setup wizard:
6
29
 
7
30
  ```bash
8
31
  npx -y @nullpay/mcp
9
32
  ```
10
33
 
11
- The setup wizard asks whether to install into Claude Code or Claude Desktop, then writes the required MCP config automatically on the user's machine.
34
+ You can also call the setup command explicitly:
12
35
 
13
- ## User-provided env
36
+ ```bash
37
+ npx -y @nullpay/mcp setup
38
+ ```
14
39
 
15
- Users only need to provide:
40
+ The wizard writes the required MCP config entry automatically.
16
41
 
17
- - `NULLPAY_MAIN_ADDRESS`
18
- - `NULLPAY_MAIN_PRIVATE_KEY`
19
- - `NULLPAY_MAIN_PASSWORD`
42
+ ## Manual Claude Configuration
20
43
 
21
- ## Tools
44
+ The setup wizard is the recommended path, but you can also add the MCP server manually.
45
+
46
+ ### Claude Desktop
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "nullpay": {
52
+ "command": "npx",
53
+ "args": ["-y", "@nullpay/mcp", "server"],
54
+ "env": {
55
+ "NULLPAY_MAIN_ADDRESS": "aleo1...",
56
+ "NULLPAY_MAIN_PRIVATE_KEY": "APrivateKey1...",
57
+ "NULLPAY_MAIN_PASSWORD": "your-password"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ On Windows, Claude Desktop may need:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "nullpay": {
70
+ "command": "cmd",
71
+ "args": ["/c", "npx", "-y", "@nullpay/mcp", "server"],
72
+ "env": {
73
+ "NULLPAY_MAIN_ADDRESS": "aleo1...",
74
+ "NULLPAY_MAIN_PRIVATE_KEY": "APrivateKey1...",
75
+ "NULLPAY_MAIN_PASSWORD": "your-password"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Claude Code
83
+
84
+ The setup wizard can also write the Claude Code config automatically. If needed, the same `server` command is used there too.
85
+
86
+ After setup, restart Claude and verify the MCP server is available.
87
+
88
+ ## Commands
89
+
90
+ ### Published package
91
+
92
+ Start the interactive installer:
93
+
94
+ ```bash
95
+ npx -y @nullpay/mcp
96
+ ```
97
+
98
+ Run the MCP server directly:
99
+
100
+ ```bash
101
+ npx -y @nullpay/mcp server
102
+ ```
103
+
104
+ Show help:
105
+
106
+ ```bash
107
+ npx -y @nullpay/mcp --help
108
+ ```
109
+
110
+ ### Local development
111
+
112
+ From `packages/nullpay-mcp`:
113
+
114
+ ```bash
115
+ npm install
116
+ npm run build
117
+ node dist/cli.js
118
+ ```
119
+
120
+ Run the local MCP server directly:
121
+
122
+ ```bash
123
+ node dist/cli.js server
124
+ ```
125
+
126
+ Run in TypeScript during development:
127
+
128
+ ```bash
129
+ npm run dev
130
+ ```
131
+
132
+ ## Tooling And Login Model
133
+
134
+ The MCP server exposes four tools:
22
135
 
23
136
  - `login`
24
137
  - `create_invoice`
25
138
  - `pay_invoice`
26
139
  - `get_transaction_info`
27
140
 
28
- ## Environment
141
+ ### `login`
142
+
143
+ `login` creates or resumes the NullPay session inside the MCP process.
144
+
145
+ Normal path:
29
146
 
30
- Bundled by NullPay inside the package:
147
+ - uses address + password from tool input or env
148
+ - loads the backend profile
149
+ - validates the password by decrypting stored wallet data
150
+ - restores burner wallet metadata when present
31
151
 
32
- - production backend URL
33
- - public NullPay base URL
34
- - Provable API key
35
- - Provable consumer ID
152
+ Recovery path:
36
153
 
37
- Optional main-wallet credentials for record-backed transaction lookup and automated main-wallet payments:
154
+ - if password is missing but `NULLPAY_MAIN_PRIVATE_KEY` is available
155
+ - the server can recover the backed-up password from on-chain backup records
156
+ - if a full burner backup exists on-chain, it restores the burner wallet automatically
157
+
158
+ ### `create_invoice`
159
+
160
+ - uses the active wallet address
161
+ - calls the NullPay backend relayer endpoint
162
+ - waits for the invoice hash to resolve from Aleo mapping
163
+ - stores the invoice row in the backend
164
+ - returns a payment link
165
+
166
+ ### `pay_invoice`
167
+
168
+ - accepts either a full NullPay payment link or an invoice hash
169
+ - prefers the full payment link so merchant address, amount, salt, token, and session id are available immediately
170
+ - builds the payment authorization locally with the user's wallet key
171
+ - sends the authorization to the backend sponsor endpoint
172
+
173
+ ### `get_transaction_info`
174
+
175
+ - fetches invoice rows from the backend
176
+ - enriches private record-backed data locally when the main private key is available
177
+
178
+ ## Credential Model
179
+
180
+ User-provided values:
38
181
 
39
182
  - `NULLPAY_MAIN_ADDRESS`
40
- - `NULLPAY_MAIN_PASSWORD`
41
183
  - `NULLPAY_MAIN_PRIVATE_KEY`
184
+ - `NULLPAY_MAIN_PASSWORD`
185
+
186
+ Bundled or backend-side values:
187
+
188
+ - `NULLPAY_BACKEND_URL` defaults internally to production
189
+ - `NULLPAY_PUBLIC_BASE_URL` defaults internally to production
190
+ - relayer secrets stay on the backend
42
191
 
43
- `NULLPAY_MAIN_PVT_KEY` is also accepted as a legacy alias for the private key.
192
+ ## Security Notes
44
193
 
45
- When Claude launches the MCP server, it also loads env values from these files if present:
194
+ - The user's private key is intended to stay local to the MCP process.
195
+ - The config file used by Claude contains sensitive wallet credentials and should be treated like a secret file.
196
+ - The MCP server never returns decrypted private keys in tool output.
197
+ - Password recovery from on-chain records only works when the main private key is available locally.
46
198
 
47
- - `packages/nullpay-mcp/.env`
48
- - repo-root `.env`
49
- - `backend/.env`
199
+ ## Publishing
50
200
 
51
- For relayed invoice creation and sponsored execution, the backend still needs:
201
+ From `packages/nullpay-mcp`:
52
202
 
53
- - `RELAYER_PRIVATE_KEY`
203
+ ```bash
204
+ npm run build
205
+ npm publish --access public
206
+ ```
207
+
208
+ If npm 2FA is enabled:
209
+
210
+ ```bash
211
+ npm publish --access public --otp=123456
212
+ ```
54
213
 
55
- ## Notes
214
+ ## License
56
215
 
57
- - Burner wallet private keys remain encrypted at rest in the existing `users` table.
58
- - The MCP server decrypts burner keys only in memory during payment execution.
59
- - If `NULLPAY_MAIN_PRIVATE_KEY` is available, the MCP server can fetch invoice amounts from main-wallet records and pay invoices from the main wallet without exposing that key to the model.
60
- - If the main private key is not available, the MCP server still allows login and invoice creation, and it prompts the user to add the env var for record-backed amount lookup and automated main-wallet payments.
216
+ MIT
package/dist/aleo.d.ts CHANGED
@@ -53,8 +53,24 @@ export declare function createInvoiceDbRecord(args: {
53
53
  }[] | null;
54
54
  for_sdk: boolean;
55
55
  };
56
+ export interface ParsedBurnerBackupRecord {
57
+ owner: string;
58
+ burnerAddress: string;
59
+ passwordPart: string;
60
+ pkParts: string[];
61
+ plaintext: string;
62
+ }
63
+ export interface RecoveredOnChainWalletBackup {
64
+ password: string;
65
+ burnerAddress?: string;
66
+ encryptedBurnerKey?: string;
67
+ source: 'password_only' | 'full_burner';
68
+ }
56
69
  export declare function parseOwnedInvoiceRecord(plaintext: string): ParsedOwnedInvoiceRecord | null;
70
+ export declare function parseBurnerBackupRecord(plaintext: string): ParsedBurnerBackupRecord | null;
57
71
  export declare function fetchOwnedInvoiceRecords(privateKey: string): Promise<ParsedOwnedInvoiceRecord[]>;
72
+ export declare function fetchOwnedBurnerBackupRecords(privateKey: string): Promise<ParsedBurnerBackupRecord[]>;
73
+ export declare function recoverOnChainWalletBackup(privateKey: string, ownerAddress: string): Promise<RecoveredOnChainWalletBackup | null>;
58
74
  export declare function fetchOwnedInvoiceRecordByHash(privateKey: string, invoiceHash: string): Promise<ParsedOwnedInvoiceRecord | null>;
59
75
  export declare function enrichInvoiceWithRecordAmount(invoice: InvoiceRecord, privateKey?: string | null): Promise<InvoiceRecord>;
60
76
  export declare function createSponsoredPaymentAuthorization(args: {
package/dist/aleo.js CHANGED
@@ -13,7 +13,10 @@ exports.invoiceTypeToNumber = invoiceTypeToNumber;
13
13
  exports.buildPaymentLink = buildPaymentLink;
14
14
  exports.createInvoiceDbRecord = createInvoiceDbRecord;
15
15
  exports.parseOwnedInvoiceRecord = parseOwnedInvoiceRecord;
16
+ exports.parseBurnerBackupRecord = parseBurnerBackupRecord;
16
17
  exports.fetchOwnedInvoiceRecords = fetchOwnedInvoiceRecords;
18
+ exports.fetchOwnedBurnerBackupRecords = fetchOwnedBurnerBackupRecords;
19
+ exports.recoverOnChainWalletBackup = recoverOnChainWalletBackup;
17
20
  exports.fetchOwnedInvoiceRecordByHash = fetchOwnedInvoiceRecordByHash;
18
21
  exports.enrichInvoiceWithRecordAmount = enrichInvoiceWithRecordAmount;
19
22
  exports.createSponsoredPaymentAuthorization = createSponsoredPaymentAuthorization;
@@ -52,6 +55,30 @@ function fieldToString(fieldVal) {
52
55
  return '';
53
56
  }
54
57
  }
58
+ function fieldChunksToString(chunks) {
59
+ let result = '';
60
+ for (const chunk of chunks) {
61
+ if (!chunk || chunk === '0field' || chunk === '0') {
62
+ continue;
63
+ }
64
+ const numeric = chunk.replace('field', '').replace('u128', '').replace('u64', '');
65
+ let value;
66
+ try {
67
+ value = BigInt(numeric);
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ let hex = value.toString(16);
73
+ if (hex.length % 2 !== 0) {
74
+ hex = '0' + hex;
75
+ }
76
+ for (let i = 0; i < hex.length; i += 2) {
77
+ result += String.fromCharCode(Number.parseInt(hex.slice(i, i + 2), 16));
78
+ }
79
+ }
80
+ return result;
81
+ }
55
82
  function parseNumericValue(value) {
56
83
  if (!value) {
57
84
  return 0;
@@ -305,6 +332,41 @@ function parseOwnedInvoiceRecord(plaintext) {
305
332
  return null;
306
333
  }
307
334
  }
335
+ function parseBurnerBackupRecord(plaintext) {
336
+ try {
337
+ const getVal = (key) => {
338
+ const regex = new RegExp(`(?:${key}|"${key}"):\\s*([\\w\\d\\.]+)`);
339
+ const match = plaintext.match(regex);
340
+ if (match && match[1]) {
341
+ return match[1].replace('.private', '').replace('.public', '');
342
+ }
343
+ return null;
344
+ };
345
+ const owner = getVal('owner');
346
+ const burnerAddress = getVal('burner_address');
347
+ const passwordPart = getVal('password_part');
348
+ if (!burnerAddress || !passwordPart) {
349
+ return null;
350
+ }
351
+ const pkParts = [];
352
+ for (let i = 1; i <= 10; i += 1) {
353
+ const part = getVal(`pk_part_${i}`);
354
+ if (part && part !== '0field' && part !== '0') {
355
+ pkParts.push(part);
356
+ }
357
+ }
358
+ return {
359
+ owner: owner || '',
360
+ burnerAddress,
361
+ passwordPart,
362
+ pkParts,
363
+ plaintext,
364
+ };
365
+ }
366
+ catch {
367
+ return null;
368
+ }
369
+ }
308
370
  async function fetchOwnedInvoiceRecords(privateKey) {
309
371
  const session = await getScannerSession(privateKey);
310
372
  const records = await fetchOwnedProgramRecords(session, exports.PROGRAM_ID);
@@ -325,6 +387,67 @@ async function fetchOwnedInvoiceRecords(privateKey) {
325
387
  }
326
388
  return parsed;
327
389
  }
390
+ async function fetchOwnedBurnerBackupRecords(privateKey) {
391
+ const session = await getScannerSession(privateKey);
392
+ const records = await fetchOwnedProgramRecords(session, exports.PROGRAM_ID);
393
+ const parsed = [];
394
+ for (const record of records) {
395
+ let plaintext = record.record_plaintext || record.plaintext || '';
396
+ if (!plaintext && record.record_ciphertext) {
397
+ const { RecordCiphertext } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
398
+ const ciphertext = RecordCiphertext.fromString(record.record_ciphertext);
399
+ plaintext = ciphertext.decrypt(session.account.viewKey()).toString();
400
+ }
401
+ if (!plaintext)
402
+ continue;
403
+ const burnerRecord = parseBurnerBackupRecord(plaintext);
404
+ if (burnerRecord) {
405
+ parsed.push(burnerRecord);
406
+ }
407
+ }
408
+ return parsed;
409
+ }
410
+ async function recoverOnChainWalletBackup(privateKey, ownerAddress) {
411
+ const records = await fetchOwnedBurnerBackupRecords(privateKey);
412
+ let passwordOnlyMatch = null;
413
+ let fullBurnerMatch = null;
414
+ for (const record of records) {
415
+ const ownerMatches = record.owner === ownerAddress;
416
+ const passwordOnlyMatches = record.burnerAddress === ownerAddress;
417
+ if (!ownerMatches && !passwordOnlyMatches) {
418
+ continue;
419
+ }
420
+ const encryptedPayload = fieldChunksToString(record.pkParts);
421
+ const hasRealPayload = Boolean(encryptedPayload && !encryptedPayload.startsWith('0'));
422
+ if (hasRealPayload) {
423
+ fullBurnerMatch = record;
424
+ }
425
+ else if (!passwordOnlyMatch) {
426
+ passwordOnlyMatch = record;
427
+ }
428
+ }
429
+ const bestMatch = fullBurnerMatch || passwordOnlyMatch;
430
+ if (!bestMatch) {
431
+ return null;
432
+ }
433
+ const password = fieldChunksToString([bestMatch.passwordPart]);
434
+ if (!password) {
435
+ return null;
436
+ }
437
+ if (fullBurnerMatch) {
438
+ const encryptedBurnerKey = fieldChunksToString(fullBurnerMatch.pkParts);
439
+ return {
440
+ password,
441
+ burnerAddress: fullBurnerMatch.burnerAddress,
442
+ encryptedBurnerKey: encryptedBurnerKey || undefined,
443
+ source: 'full_burner',
444
+ };
445
+ }
446
+ return {
447
+ password,
448
+ source: 'password_only',
449
+ };
450
+ }
328
451
  async function fetchOwnedInvoiceRecordByHash(privateKey, invoiceHash) {
329
452
  const normalized = normalizeInvoiceHash(invoiceHash);
330
453
  const records = await fetchOwnedInvoiceRecords(privateKey);
package/dist/env.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_BACKEND_URL = "http://localhost:3000/api";
1
+ export declare const DEFAULT_BACKEND_URL = "https://nullpay-backend-ib5q4.ondigitalocean.app/api";
2
2
  export declare const DEFAULT_PUBLIC_BASE_URL = "https://nullpay.app";
3
3
  export declare function parseDotEnv(content: string): Record<string, string>;
4
4
  export declare function loadEnvFiles(): void;
package/dist/env.js CHANGED
@@ -10,7 +10,7 @@ exports.getRuntimeConfig = getRuntimeConfig;
10
10
  exports.getProvableConfig = getProvableConfig;
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
- exports.DEFAULT_BACKEND_URL = 'http://localhost:3000/api';
13
+ exports.DEFAULT_BACKEND_URL = 'https://nullpay-backend-ib5q4.ondigitalocean.app/api';
14
14
  exports.DEFAULT_PUBLIC_BASE_URL = 'https://nullpay.app';
15
15
  const DEFAULT_PROVABLE_API_KEY = 'tWR9YWkM5SVmx1u3m7My8S4p4e2s84Oe';
16
16
  const DEFAULT_PROVABLE_CONSUMER_ID = '73ba1b21-d8f7-4d7f-bfd9-0408a4e183f3';
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
- const password = args.password || envMain.password || '';
225
+ let password = args.password || envMain.password || '';
226
226
  const mainPrivateKey = args.main_private_key || envMain.privateKey || null;
227
- if (!address || !password) {
228
- throw new Error('Address and password are required. You can pass them directly or set NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD in env.');
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
- const decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
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
- throw new Error('Password is incorrect for this NullPay account.');
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
- burnerAddress = await (0, crypto_1.decryptWithPassword)(encryptedBurnerAddress, password);
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.' : `Logged in as ${address}.`,
345
+ usedEnvCredentials ? 'Used main-wallet address and password from MCP env.' : 'Logged in as ' + address + '.',
286
346
  encryptedBurnerKey
287
- ? `Active wallet: ${preferredWallet}. Burner wallet is available${burnerAddress ? ` at ${burnerAddress}` : ''}.`
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/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
- "name": "@nullpay/mcp",
3
- "version": "1.0.1",
4
- "description": "NullPay MCP server and Claude setup wizard for conversational payment flows",
5
- "type": "commonjs",
6
- "main": "dist/server.js",
7
- "types": "dist/server.d.ts",
8
- "bin": {
9
- "nullpay-mcp": "./dist/cli.js"
10
- },
11
- "files": [
12
- "dist",
13
- "README.md"
14
- ],
15
- "scripts": {
16
- "build": "tsc -p tsconfig.json",
17
- "start": "node dist/cli.js server",
18
- "dev": "ts-node src/cli.ts server",
19
- "setup": "ts-node src/cli.ts setup",
20
- "prepublishOnly": "npm run build"
21
- },
22
- "publishConfig": {
23
- "access": "public"
24
- },
25
- "dependencies": {
26
- "@provablehq/sdk": "^0.9.18",
27
- "@provablehq/wasm": "^0.9.18"
28
- },
29
- "devDependencies": {
30
- "@types/node": "^25.4.0",
31
- "ts-node": "^10.9.2",
32
- "typescript": "^5.9.3"
33
- }
2
+ "name": "@nullpay/mcp",
3
+ "version": "1.0.3",
4
+ "description": "NullPay MCP server and Claude setup wizard for conversational payment flows",
5
+ "type": "commonjs",
6
+ "main": "dist/server.js",
7
+ "types": "dist/server.d.ts",
8
+ "bin": {
9
+ "nullpay-mcp": "./dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "start": "node dist/cli.js server",
18
+ "dev": "ts-node src/cli.ts server",
19
+ "setup": "ts-node src/cli.ts setup",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@provablehq/sdk": "^0.9.18",
27
+ "@provablehq/wasm": "^0.9.18"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^25.4.0",
31
+ "ts-node": "^10.9.2",
32
+ "typescript": "^5.9.3"
33
+ }
34
34
  }