@nullpay/mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -37
- package/dist/aleo.js +2 -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/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,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NullPay MCP
|
|
2
2
|
|
|
3
|
-
NullPay MCP
|
|
3
|
+
NullPay MCP installs a local MCP server for Claude and lets users connect with only their wallet credentials.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npx -y @nullpay/mcp
|
|
9
|
+
```
|
|
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
|
+
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.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## User-provided env
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
npm install @nullpay/mcp
|
|
17
|
-
```
|
|
15
|
+
Users only need to provide:
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
- `NULLPAY_MAIN_ADDRESS`
|
|
18
|
+
- `NULLPAY_MAIN_PRIVATE_KEY`
|
|
19
|
+
- `NULLPAY_MAIN_PASSWORD`
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
- `login`
|
|
24
|
+
- `create_invoice`
|
|
25
|
+
- `pay_invoice`
|
|
26
|
+
- `get_transaction_info`
|
|
27
|
+
|
|
28
|
+
## Environment
|
|
29
|
+
|
|
30
|
+
Bundled by NullPay inside the package:
|
|
31
|
+
|
|
32
|
+
- production backend URL
|
|
33
|
+
- public NullPay base URL
|
|
34
|
+
- Provable API key
|
|
35
|
+
- Provable consumer ID
|
|
36
|
+
|
|
37
|
+
Optional main-wallet credentials for record-backed transaction lookup and automated main-wallet payments:
|
|
38
|
+
|
|
39
|
+
- `NULLPAY_MAIN_ADDRESS`
|
|
40
|
+
- `NULLPAY_MAIN_PASSWORD`
|
|
41
|
+
- `NULLPAY_MAIN_PRIVATE_KEY`
|
|
42
|
+
|
|
43
|
+
`NULLPAY_MAIN_PVT_KEY` is also accepted as a legacy alias for the private key.
|
|
44
|
+
|
|
45
|
+
When Claude launches the MCP server, it also loads env values from these files if present:
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
- `packages/nullpay-mcp/.env`
|
|
48
|
+
- repo-root `.env`
|
|
49
|
+
- `backend/.env`
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
For relayed invoice creation and sponsored execution, the backend still needs:
|
|
43
52
|
|
|
44
|
-
- `
|
|
45
|
-
- `NULLPAY_MCP_SHARED_SECRET`: A shared secret to authenticate with the backend.
|
|
53
|
+
- `RELAYER_PRIVATE_KEY`
|
|
46
54
|
|
|
47
|
-
##
|
|
55
|
+
## Notes
|
|
48
56
|
|
|
49
|
-
|
|
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.
|
package/dist/aleo.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.enrichInvoiceWithRecordAmount = enrichInvoiceWithRecordAmount;
|
|
|
19
19
|
exports.createSponsoredPaymentAuthorization = createSponsoredPaymentAuthorization;
|
|
20
20
|
const crypto_1 = __importDefault(require("crypto"));
|
|
21
21
|
const esm_1 = require("./esm");
|
|
22
|
+
const env_1 = require("./env");
|
|
22
23
|
exports.PROGRAM_ID = 'zk_pay_proofs_privacy_v22.aleo';
|
|
23
24
|
const FREEZELIST_PROGRAM_ID = 'test_usdcx_freezelist.aleo';
|
|
24
25
|
const EXPLORER_BASE = 'https://api.explorer.provable.com/v1';
|
|
@@ -206,12 +207,8 @@ function resolvePaymentMode(invoice, fallbackAmount, fallbackCurrency) {
|
|
|
206
207
|
amountSuffix: 'u64',
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
|
-
function getProvableConsumerId() {
|
|
210
|
-
return process.env.PROVABLE_CONSUMER_ID || process.env.PROVABLE_CONSUMER_KEY;
|
|
211
|
-
}
|
|
212
210
|
async function getScannerSession(privateKey) {
|
|
213
|
-
const provableApiKey =
|
|
214
|
-
const consumerId = getProvableConsumerId();
|
|
211
|
+
const { apiKey: provableApiKey, consumerId } = (0, env_1.getProvableConfig)();
|
|
215
212
|
if (!provableApiKey || !consumerId) {
|
|
216
213
|
throw new Error('PROVABLE_API_KEY and PROVABLE_CONSUMER_ID/PROVABLE_CONSUMER_KEY are required for record fetching and payment automation.');
|
|
217
214
|
}
|
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 = "http://localhost:3000/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 = 'http://localhost:3000/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;
|
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/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;
|
package/dist/utils/formatters.js
CHANGED
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeCurrency = normalizeCurrency;
|
|
4
|
-
exports.normalizePaymentCurrency = normalizePaymentCurrency;
|
|
5
|
-
exports.normalizeInvoiceType = normalizeInvoiceType;
|
|
6
|
-
exports.tokenTypeLabel = tokenTypeLabel;
|
|
7
|
-
exports.invoiceTypeLabel = invoiceTypeLabel;
|
|
8
|
-
exports.currencyToTokenType = currencyToTokenType;
|
|
9
|
-
exports.linkTokenToCurrency = linkTokenToCurrency;
|
|
10
|
-
exports.linkTypeToInvoiceType = linkTypeToInvoiceType;
|
|
11
|
-
exports.parseAmount = parseAmount;
|
|
12
|
-
exports.shouldMarkInvoiceSettled = shouldMarkInvoiceSettled;
|
|
13
|
-
exports.formatInvoiceSummary = formatInvoiceSummary;
|
|
14
|
-
exports.getAmountSource = getAmountSource;
|
|
15
|
-
exports.buildAmountLookupHint = buildAmountLookupHint;
|
|
16
|
-
function normalizeCurrency(value) {
|
|
17
|
-
const normalized = (value || 'CREDITS').toUpperCase();
|
|
18
|
-
if (normalized === 'USDCX' || normalized === 'USAD' || normalized === 'ANY') {
|
|
19
|
-
return normalized;
|
|
20
|
-
}
|
|
21
|
-
return 'CREDITS';
|
|
22
|
-
}
|
|
23
|
-
function normalizePaymentCurrency(value) {
|
|
24
|
-
if (!value) {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
const normalized = value.toUpperCase();
|
|
28
|
-
if (normalized === 'USDCX' || normalized === 'USAD') {
|
|
29
|
-
return normalized;
|
|
30
|
-
}
|
|
31
|
-
return 'CREDITS';
|
|
32
|
-
}
|
|
33
|
-
function normalizeInvoiceType(value) {
|
|
34
|
-
if (value === 'multipay' || value === 'donation')
|
|
35
|
-
return value;
|
|
36
|
-
return 'standard';
|
|
37
|
-
}
|
|
38
|
-
function tokenTypeLabel(tokenType) {
|
|
39
|
-
if (tokenType === 1)
|
|
40
|
-
return 'USDCX';
|
|
41
|
-
if (tokenType === 2)
|
|
42
|
-
return 'USAD';
|
|
43
|
-
if (tokenType === 3)
|
|
44
|
-
return 'ANY';
|
|
45
|
-
return 'CREDITS';
|
|
46
|
-
}
|
|
47
|
-
function invoiceTypeLabel(invoiceType) {
|
|
48
|
-
if (invoiceType === 1)
|
|
49
|
-
return 'multipay';
|
|
50
|
-
if (invoiceType === 2)
|
|
51
|
-
return 'donation';
|
|
52
|
-
return 'standard';
|
|
53
|
-
}
|
|
54
|
-
function currencyToTokenType(currency) {
|
|
55
|
-
if (currency === 'USDCX')
|
|
56
|
-
return 1;
|
|
57
|
-
if (currency === 'USAD')
|
|
58
|
-
return 2;
|
|
59
|
-
if (currency === 'ANY')
|
|
60
|
-
return 3;
|
|
61
|
-
return 0;
|
|
62
|
-
}
|
|
63
|
-
function linkTokenToCurrency(token) {
|
|
64
|
-
if (!token) {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
const normalized = token.trim().toLowerCase();
|
|
68
|
-
if (normalized === 'usdcx')
|
|
69
|
-
return 'USDCX';
|
|
70
|
-
if (normalized === 'usad')
|
|
71
|
-
return 'USAD';
|
|
72
|
-
if (normalized === 'any')
|
|
73
|
-
return 'ANY';
|
|
74
|
-
return 'CREDITS';
|
|
75
|
-
}
|
|
76
|
-
function linkTypeToInvoiceType(type) {
|
|
77
|
-
if (type === 'multipay' || type === 'donation') {
|
|
78
|
-
return type;
|
|
79
|
-
}
|
|
80
|
-
return 'standard';
|
|
81
|
-
}
|
|
82
|
-
function parseAmount(value) {
|
|
83
|
-
if (typeof value === 'number') {
|
|
84
|
-
return Number.isFinite(value) ? value : undefined;
|
|
85
|
-
}
|
|
86
|
-
if (!value) {
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
const parsed = Number(value);
|
|
90
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
91
|
-
}
|
|
92
|
-
function shouldMarkInvoiceSettled(invoiceType) {
|
|
93
|
-
return invoiceType !== 1 && invoiceType !== 2;
|
|
94
|
-
}
|
|
95
|
-
function formatInvoiceSummary(invoice) {
|
|
96
|
-
const paymentIds = Array.isArray(invoice.payment_tx_ids) && invoice.payment_tx_ids.length > 0
|
|
97
|
-
? invoice.payment_tx_ids.join(', ')
|
|
98
|
-
: 'none';
|
|
99
|
-
const amount = invoice.amount ?? 0;
|
|
100
|
-
return [
|
|
101
|
-
`invoice=${invoice.invoice_hash}`,
|
|
102
|
-
`status=${invoice.status}`,
|
|
103
|
-
`amount=${amount}`,
|
|
104
|
-
`token=${tokenTypeLabel(invoice.token_type)}`,
|
|
105
|
-
`type=${invoiceTypeLabel(invoice.invoice_type)}`,
|
|
106
|
-
`created=${invoice.created_at || 'unknown'}`,
|
|
107
|
-
`invoice_tx=${invoice.invoice_transaction_id || 'none'}`,
|
|
108
|
-
`payment_txs=${paymentIds}`
|
|
109
|
-
].join(' | ');
|
|
110
|
-
}
|
|
111
|
-
function getAmountSource(invoice) {
|
|
112
|
-
if (typeof invoice.amount_micro === 'number') {
|
|
113
|
-
return 'record';
|
|
114
|
-
}
|
|
115
|
-
if (typeof invoice.amount === 'number' && invoice.amount > 0) {
|
|
116
|
-
return 'database';
|
|
117
|
-
}
|
|
118
|
-
return 'missing';
|
|
119
|
-
}
|
|
120
|
-
function buildAmountLookupHint(invoice, hasInvoiceLookupKey) {
|
|
121
|
-
const amountSource = getAmountSource(invoice);
|
|
122
|
-
if (amountSource === 'record') {
|
|
123
|
-
return ' | amount_source=record';
|
|
124
|
-
}
|
|
125
|
-
if (amountSource === 'database') {
|
|
126
|
-
return ' | amount_source=database';
|
|
127
|
-
}
|
|
128
|
-
if (hasInvoiceLookupKey) {
|
|
129
|
-
return ' | amount_source=missing | record_lookup=not_found_or_unreadable_for_selected_wallet';
|
|
130
|
-
}
|
|
131
|
-
return ' | amount_source=db_only (private key missing to fetch record-backed amount)';
|
|
132
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeCurrency = normalizeCurrency;
|
|
4
|
+
exports.normalizePaymentCurrency = normalizePaymentCurrency;
|
|
5
|
+
exports.normalizeInvoiceType = normalizeInvoiceType;
|
|
6
|
+
exports.tokenTypeLabel = tokenTypeLabel;
|
|
7
|
+
exports.invoiceTypeLabel = invoiceTypeLabel;
|
|
8
|
+
exports.currencyToTokenType = currencyToTokenType;
|
|
9
|
+
exports.linkTokenToCurrency = linkTokenToCurrency;
|
|
10
|
+
exports.linkTypeToInvoiceType = linkTypeToInvoiceType;
|
|
11
|
+
exports.parseAmount = parseAmount;
|
|
12
|
+
exports.shouldMarkInvoiceSettled = shouldMarkInvoiceSettled;
|
|
13
|
+
exports.formatInvoiceSummary = formatInvoiceSummary;
|
|
14
|
+
exports.getAmountSource = getAmountSource;
|
|
15
|
+
exports.buildAmountLookupHint = buildAmountLookupHint;
|
|
16
|
+
function normalizeCurrency(value) {
|
|
17
|
+
const normalized = (value || 'CREDITS').toUpperCase();
|
|
18
|
+
if (normalized === 'USDCX' || normalized === 'USAD' || normalized === 'ANY') {
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
return 'CREDITS';
|
|
22
|
+
}
|
|
23
|
+
function normalizePaymentCurrency(value) {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const normalized = value.toUpperCase();
|
|
28
|
+
if (normalized === 'USDCX' || normalized === 'USAD') {
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
return 'CREDITS';
|
|
32
|
+
}
|
|
33
|
+
function normalizeInvoiceType(value) {
|
|
34
|
+
if (value === 'multipay' || value === 'donation')
|
|
35
|
+
return value;
|
|
36
|
+
return 'standard';
|
|
37
|
+
}
|
|
38
|
+
function tokenTypeLabel(tokenType) {
|
|
39
|
+
if (tokenType === 1)
|
|
40
|
+
return 'USDCX';
|
|
41
|
+
if (tokenType === 2)
|
|
42
|
+
return 'USAD';
|
|
43
|
+
if (tokenType === 3)
|
|
44
|
+
return 'ANY';
|
|
45
|
+
return 'CREDITS';
|
|
46
|
+
}
|
|
47
|
+
function invoiceTypeLabel(invoiceType) {
|
|
48
|
+
if (invoiceType === 1)
|
|
49
|
+
return 'multipay';
|
|
50
|
+
if (invoiceType === 2)
|
|
51
|
+
return 'donation';
|
|
52
|
+
return 'standard';
|
|
53
|
+
}
|
|
54
|
+
function currencyToTokenType(currency) {
|
|
55
|
+
if (currency === 'USDCX')
|
|
56
|
+
return 1;
|
|
57
|
+
if (currency === 'USAD')
|
|
58
|
+
return 2;
|
|
59
|
+
if (currency === 'ANY')
|
|
60
|
+
return 3;
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
function linkTokenToCurrency(token) {
|
|
64
|
+
if (!token) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const normalized = token.trim().toLowerCase();
|
|
68
|
+
if (normalized === 'usdcx')
|
|
69
|
+
return 'USDCX';
|
|
70
|
+
if (normalized === 'usad')
|
|
71
|
+
return 'USAD';
|
|
72
|
+
if (normalized === 'any')
|
|
73
|
+
return 'ANY';
|
|
74
|
+
return 'CREDITS';
|
|
75
|
+
}
|
|
76
|
+
function linkTypeToInvoiceType(type) {
|
|
77
|
+
if (type === 'multipay' || type === 'donation') {
|
|
78
|
+
return type;
|
|
79
|
+
}
|
|
80
|
+
return 'standard';
|
|
81
|
+
}
|
|
82
|
+
function parseAmount(value) {
|
|
83
|
+
if (typeof value === 'number') {
|
|
84
|
+
return Number.isFinite(value) ? value : undefined;
|
|
85
|
+
}
|
|
86
|
+
if (!value) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const parsed = Number(value);
|
|
90
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
91
|
+
}
|
|
92
|
+
function shouldMarkInvoiceSettled(invoiceType) {
|
|
93
|
+
return invoiceType !== 1 && invoiceType !== 2;
|
|
94
|
+
}
|
|
95
|
+
function formatInvoiceSummary(invoice) {
|
|
96
|
+
const paymentIds = Array.isArray(invoice.payment_tx_ids) && invoice.payment_tx_ids.length > 0
|
|
97
|
+
? invoice.payment_tx_ids.join(', ')
|
|
98
|
+
: 'none';
|
|
99
|
+
const amount = invoice.amount ?? 0;
|
|
100
|
+
return [
|
|
101
|
+
`invoice=${invoice.invoice_hash}`,
|
|
102
|
+
`status=${invoice.status}`,
|
|
103
|
+
`amount=${amount}`,
|
|
104
|
+
`token=${tokenTypeLabel(invoice.token_type)}`,
|
|
105
|
+
`type=${invoiceTypeLabel(invoice.invoice_type)}`,
|
|
106
|
+
`created=${invoice.created_at || 'unknown'}`,
|
|
107
|
+
`invoice_tx=${invoice.invoice_transaction_id || 'none'}`,
|
|
108
|
+
`payment_txs=${paymentIds}`
|
|
109
|
+
].join(' | ');
|
|
110
|
+
}
|
|
111
|
+
function getAmountSource(invoice) {
|
|
112
|
+
if (typeof invoice.amount_micro === 'number') {
|
|
113
|
+
return 'record';
|
|
114
|
+
}
|
|
115
|
+
if (typeof invoice.amount === 'number' && invoice.amount > 0) {
|
|
116
|
+
return 'database';
|
|
117
|
+
}
|
|
118
|
+
return 'missing';
|
|
119
|
+
}
|
|
120
|
+
function buildAmountLookupHint(invoice, hasInvoiceLookupKey) {
|
|
121
|
+
const amountSource = getAmountSource(invoice);
|
|
122
|
+
if (amountSource === 'record') {
|
|
123
|
+
return ' | amount_source=record';
|
|
124
|
+
}
|
|
125
|
+
if (amountSource === 'database') {
|
|
126
|
+
return ' | amount_source=database';
|
|
127
|
+
}
|
|
128
|
+
if (hasInvoiceLookupKey) {
|
|
129
|
+
return ' | amount_source=missing | record_lookup=not_found_or_unreadable_for_selected_wallet';
|
|
130
|
+
}
|
|
131
|
+
return ' | amount_source=db_only (private key missing to fetch record-backed amount)';
|
|
132
|
+
}
|
package/package.json
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/node": "^25.4.0",
|
|
36
|
-
"ts-node": "^10.9.2",
|
|
37
|
-
"typescript": "^5.9.3"
|
|
38
|
-
}
|
|
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
|
+
}
|
|
39
34
|
}
|