@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/README.md
CHANGED
|
@@ -1,49 +1,216 @@
|
|
|
1
1
|
# @nullpay/mcp
|
|
2
2
|
|
|
3
|
-
NullPay MCP
|
|
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
|
-
|
|
5
|
+
The package is designed so the user only needs to provide their own wallet credentials:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- `NULLPAY_MAIN_ADDRESS`
|
|
8
|
+
- `NULLPAY_MAIN_PRIVATE_KEY`
|
|
9
|
+
- `NULLPAY_MAIN_PASSWORD`
|
|
8
10
|
|
|
9
|
-
-
|
|
10
|
-
- **Privacy First**: Built on top of the Aleo blockchain with Zero-Knowledge Proofs.
|
|
11
|
-
- **Stdio Transport**: Compatible with MCP clients like Claude Desktop.
|
|
11
|
+
NullPay-owned infrastructure stays inside the package or on the backend:
|
|
12
12
|
|
|
13
|
-
|
|
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:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx -y @nullpay/mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You can also call the setup command explicitly:
|
|
14
35
|
|
|
15
36
|
```bash
|
|
16
|
-
|
|
37
|
+
npx -y @nullpay/mcp setup
|
|
17
38
|
```
|
|
18
39
|
|
|
19
|
-
|
|
40
|
+
The wizard writes the required MCP config entry automatically.
|
|
20
41
|
|
|
21
|
-
|
|
42
|
+
## Manual Claude Configuration
|
|
22
43
|
|
|
23
|
-
|
|
44
|
+
The setup wizard is the recommended path, but you can also add the MCP server manually.
|
|
45
|
+
|
|
46
|
+
### Claude Desktop
|
|
24
47
|
|
|
25
48
|
```json
|
|
26
49
|
{
|
|
27
50
|
"mcpServers": {
|
|
28
51
|
"nullpay": {
|
|
29
52
|
"command": "npx",
|
|
30
|
-
"args": ["-y", "@nullpay/mcp"],
|
|
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"],
|
|
31
72
|
"env": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
73
|
+
"NULLPAY_MAIN_ADDRESS": "aleo1...",
|
|
74
|
+
"NULLPAY_MAIN_PRIVATE_KEY": "APrivateKey1...",
|
|
75
|
+
"NULLPAY_MAIN_PASSWORD": "your-password"
|
|
34
76
|
}
|
|
35
77
|
}
|
|
36
78
|
}
|
|
37
79
|
}
|
|
38
80
|
```
|
|
39
81
|
|
|
40
|
-
|
|
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:
|
|
41
99
|
|
|
42
|
-
|
|
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`:
|
|
43
113
|
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
135
|
+
|
|
136
|
+
- `login`
|
|
137
|
+
- `create_invoice`
|
|
138
|
+
- `pay_invoice`
|
|
139
|
+
- `get_transaction_info`
|
|
140
|
+
|
|
141
|
+
### `login`
|
|
142
|
+
|
|
143
|
+
`login` creates or resumes the NullPay session inside the MCP process.
|
|
144
|
+
|
|
145
|
+
Normal path:
|
|
146
|
+
|
|
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
|
|
151
|
+
|
|
152
|
+
Recovery path:
|
|
153
|
+
|
|
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:
|
|
181
|
+
|
|
182
|
+
- `NULLPAY_MAIN_ADDRESS`
|
|
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
|
|
191
|
+
|
|
192
|
+
## Security Notes
|
|
193
|
+
|
|
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.
|
|
198
|
+
|
|
199
|
+
## Publishing
|
|
200
|
+
|
|
201
|
+
From `packages/nullpay-mcp`:
|
|
202
|
+
|
|
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
|
+
```
|
|
46
213
|
|
|
47
214
|
## License
|
|
48
215
|
|
|
49
|
-
MIT
|
|
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,12 +13,16 @@ 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;
|
|
20
23
|
const crypto_1 = __importDefault(require("crypto"));
|
|
21
24
|
const esm_1 = require("./esm");
|
|
25
|
+
const env_1 = require("./env");
|
|
22
26
|
exports.PROGRAM_ID = 'zk_pay_proofs_privacy_v22.aleo';
|
|
23
27
|
const FREEZELIST_PROGRAM_ID = 'test_usdcx_freezelist.aleo';
|
|
24
28
|
const EXPLORER_BASE = 'https://api.explorer.provable.com/v1';
|
|
@@ -51,6 +55,30 @@ function fieldToString(fieldVal) {
|
|
|
51
55
|
return '';
|
|
52
56
|
}
|
|
53
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
|
+
}
|
|
54
82
|
function parseNumericValue(value) {
|
|
55
83
|
if (!value) {
|
|
56
84
|
return 0;
|
|
@@ -206,12 +234,8 @@ function resolvePaymentMode(invoice, fallbackAmount, fallbackCurrency) {
|
|
|
206
234
|
amountSuffix: 'u64',
|
|
207
235
|
};
|
|
208
236
|
}
|
|
209
|
-
function getProvableConsumerId() {
|
|
210
|
-
return process.env.PROVABLE_CONSUMER_ID || process.env.PROVABLE_CONSUMER_KEY;
|
|
211
|
-
}
|
|
212
237
|
async function getScannerSession(privateKey) {
|
|
213
|
-
const provableApiKey =
|
|
214
|
-
const consumerId = getProvableConsumerId();
|
|
238
|
+
const { apiKey: provableApiKey, consumerId } = (0, env_1.getProvableConfig)();
|
|
215
239
|
if (!provableApiKey || !consumerId) {
|
|
216
240
|
throw new Error('PROVABLE_API_KEY and PROVABLE_CONSUMER_ID/PROVABLE_CONSUMER_KEY are required for record fetching and payment automation.');
|
|
217
241
|
}
|
|
@@ -308,6 +332,41 @@ function parseOwnedInvoiceRecord(plaintext) {
|
|
|
308
332
|
return null;
|
|
309
333
|
}
|
|
310
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
|
+
}
|
|
311
370
|
async function fetchOwnedInvoiceRecords(privateKey) {
|
|
312
371
|
const session = await getScannerSession(privateKey);
|
|
313
372
|
const records = await fetchOwnedProgramRecords(session, exports.PROGRAM_ID);
|
|
@@ -328,6 +387,67 @@ async function fetchOwnedInvoiceRecords(privateKey) {
|
|
|
328
387
|
}
|
|
329
388
|
return parsed;
|
|
330
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
|
+
}
|
|
331
451
|
async function fetchOwnedInvoiceRecordByHash(privateKey, invoiceHash) {
|
|
332
452
|
const normalized = normalizeInvoiceHash(invoiceHash);
|
|
333
453
|
const records = await fetchOwnedInvoiceRecords(privateKey);
|
package/dist/backend-client.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { InvoiceRecord, UserProfile } from './types';
|
|
2
2
|
export declare class NullPayBackendClient {
|
|
3
3
|
private readonly baseUrl;
|
|
4
|
-
|
|
5
|
-
constructor(baseUrl: string, mcpSecret?: string | undefined);
|
|
4
|
+
constructor(baseUrl: string);
|
|
6
5
|
private buildUrl;
|
|
7
6
|
private request;
|
|
8
7
|
getUserProfile(addressHash: string): Promise<UserProfile | null>;
|
package/dist/backend-client.js
CHANGED
|
@@ -2,20 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NullPayBackendClient = void 0;
|
|
4
4
|
function mapBackendError(path, text) {
|
|
5
|
-
if (path === '/mcp/relay/create-invoice') {
|
|
6
|
-
if (text.includes('NULLPAY_MCP_SHARED_SECRET is not configured')) {
|
|
7
|
-
return 'Invoice creation is blocked because NULLPAY_MCP_SHARED_SECRET is missing on the backend. Set the same NULLPAY_MCP_SHARED_SECRET value in both the backend env and the MCP server env, then restart both processes.';
|
|
8
|
-
}
|
|
9
|
-
if (text.includes('Invalid MCP shared secret')) {
|
|
10
|
-
return 'Invoice creation is blocked because the MCP shared secret does not match. Set the same NULLPAY_MCP_SHARED_SECRET value in both the backend env and the MCP server env, then restart both processes.';
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
5
|
return text;
|
|
14
6
|
}
|
|
15
7
|
class NullPayBackendClient {
|
|
16
|
-
constructor(baseUrl
|
|
8
|
+
constructor(baseUrl) {
|
|
17
9
|
this.baseUrl = baseUrl;
|
|
18
|
-
this.mcpSecret = mcpSecret;
|
|
19
10
|
}
|
|
20
11
|
buildUrl(path) {
|
|
21
12
|
return `${this.baseUrl.replace(/\/+$/, '')}${path}`;
|
|
@@ -75,10 +66,7 @@ class NullPayBackendClient {
|
|
|
75
66
|
async relayCreateInvoice(body) {
|
|
76
67
|
return await this.request('/mcp/relay/create-invoice', {
|
|
77
68
|
method: 'POST',
|
|
78
|
-
headers: {
|
|
79
|
-
'Content-Type': 'application/json',
|
|
80
|
-
...(this.mcpSecret ? { 'x-nullpay-mcp-secret': this.mcpSecret } : {})
|
|
81
|
-
},
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
70
|
body: JSON.stringify(body),
|
|
83
71
|
});
|
|
84
72
|
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const server_1 = require("./server");
|
|
5
|
+
const setup_1 = require("./setup");
|
|
6
|
+
function printHelp() {
|
|
7
|
+
console.log('NullPay MCP');
|
|
8
|
+
console.log('');
|
|
9
|
+
console.log('Usage:');
|
|
10
|
+
console.log(' nullpay-mcp Start the setup wizard');
|
|
11
|
+
console.log(' nullpay-mcp setup Start the setup wizard');
|
|
12
|
+
console.log(' nullpay-mcp server Run the stdio MCP server');
|
|
13
|
+
}
|
|
14
|
+
async function main() {
|
|
15
|
+
const mode = (process.argv[2] || 'setup').toLowerCase();
|
|
16
|
+
if (mode === 'server') {
|
|
17
|
+
(0, server_1.startServer)();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (mode === 'setup') {
|
|
21
|
+
await (0, setup_1.runSetupWizard)();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (mode === '--help' || mode === '-h' || mode === 'help') {
|
|
25
|
+
printHelp();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
console.error(`Unknown command: ${mode}`);
|
|
29
|
+
printHelp();
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
void main().catch((error) => {
|
|
33
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_BACKEND_URL = "https://nullpay-backend-ib5q4.ondigitalocean.app/api";
|
|
2
|
+
export declare const DEFAULT_PUBLIC_BASE_URL = "https://nullpay.app";
|
|
3
|
+
export declare function parseDotEnv(content: string): Record<string, string>;
|
|
4
|
+
export declare function loadEnvFiles(): void;
|
|
5
|
+
export declare function getRuntimeConfig(): {
|
|
6
|
+
backendBaseUrl: string;
|
|
7
|
+
publicBaseUrl: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function getProvableConfig(): {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
consumerId: string;
|
|
12
|
+
};
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
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.DEFAULT_PUBLIC_BASE_URL = exports.DEFAULT_BACKEND_URL = void 0;
|
|
7
|
+
exports.parseDotEnv = parseDotEnv;
|
|
8
|
+
exports.loadEnvFiles = loadEnvFiles;
|
|
9
|
+
exports.getRuntimeConfig = getRuntimeConfig;
|
|
10
|
+
exports.getProvableConfig = getProvableConfig;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
exports.DEFAULT_BACKEND_URL = 'https://nullpay-backend-ib5q4.ondigitalocean.app/api';
|
|
14
|
+
exports.DEFAULT_PUBLIC_BASE_URL = 'https://nullpay.app';
|
|
15
|
+
const DEFAULT_PROVABLE_API_KEY = 'tWR9YWkM5SVmx1u3m7My8S4p4e2s84Oe';
|
|
16
|
+
const DEFAULT_PROVABLE_CONSUMER_ID = '73ba1b21-d8f7-4d7f-bfd9-0408a4e183f3';
|
|
17
|
+
function parseDotEnv(content) {
|
|
18
|
+
const parsed = {};
|
|
19
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
20
|
+
const line = rawLine.trim();
|
|
21
|
+
if (!line || line.startsWith('#')) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
|
|
25
|
+
const separatorIndex = normalized.indexOf('=');
|
|
26
|
+
if (separatorIndex <= 0) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const key = normalized.slice(0, separatorIndex).trim();
|
|
30
|
+
let value = normalized.slice(separatorIndex + 1).trim();
|
|
31
|
+
if (!key) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
35
|
+
value = value.slice(1, -1);
|
|
36
|
+
}
|
|
37
|
+
parsed[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
function loadEnvFiles() {
|
|
42
|
+
const packageRoot = path_1.default.resolve(__dirname, '..');
|
|
43
|
+
const repoRoot = path_1.default.resolve(packageRoot, '..', '..');
|
|
44
|
+
const candidates = [
|
|
45
|
+
path_1.default.resolve(process.cwd(), '.env'),
|
|
46
|
+
path_1.default.resolve(packageRoot, '.env'),
|
|
47
|
+
path_1.default.resolve(repoRoot, '.env'),
|
|
48
|
+
path_1.default.resolve(repoRoot, 'backend', '.env'),
|
|
49
|
+
];
|
|
50
|
+
for (const filePath of candidates) {
|
|
51
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const values = parseDotEnv(fs_1.default.readFileSync(filePath, 'utf8'));
|
|
55
|
+
for (const [key, value] of Object.entries(values)) {
|
|
56
|
+
if (!process.env[key]) {
|
|
57
|
+
process.env[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getRuntimeConfig() {
|
|
63
|
+
loadEnvFiles();
|
|
64
|
+
return {
|
|
65
|
+
backendBaseUrl: process.env.NULLPAY_BACKEND_URL || exports.DEFAULT_BACKEND_URL,
|
|
66
|
+
publicBaseUrl: process.env.NULLPAY_PUBLIC_BASE_URL || exports.DEFAULT_PUBLIC_BASE_URL,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getProvableConfig() {
|
|
70
|
+
loadEnvFiles();
|
|
71
|
+
return {
|
|
72
|
+
apiKey: process.env.PROVABLE_API_KEY || DEFAULT_PROVABLE_API_KEY,
|
|
73
|
+
consumerId: process.env.PROVABLE_CONSUMER_ID || process.env.PROVABLE_CONSUMER_KEY || DEFAULT_PROVABLE_CONSUMER_ID,
|
|
74
|
+
};
|
|
75
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function startServer(): void;
|