@mcpaid/sdk 2.0.0
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/LICENSE +176 -0
- package/NPM_RESTRUCTURE.md +86 -0
- package/OPERATOR-LICENSE.md +18 -0
- package/README.md +380 -0
- package/SELF-HOSTING.md +108 -0
- package/dist/circuit-breaker/circuit-breaker.d.ts +26 -0
- package/dist/circuit-breaker/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker/circuit-breaker.js +128 -0
- package/dist/circuit-breaker/circuit-breaker.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1560 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts/ToolPayRouter.d.ts +288 -0
- package/dist/contracts/ToolPayRouter.d.ts.map +1 -0
- package/dist/contracts/ToolPayRouter.js +370 -0
- package/dist/contracts/ToolPayRouter.js.map +1 -0
- package/dist/contracts/router-registry.d.ts +19 -0
- package/dist/contracts/router-registry.d.ts.map +1 -0
- package/dist/contracts/router-registry.js +28 -0
- package/dist/contracts/router-registry.js.map +1 -0
- package/dist/fee/fee-calculator.d.ts +35 -0
- package/dist/fee/fee-calculator.d.ts.map +1 -0
- package/dist/fee/fee-calculator.js +57 -0
- package/dist/fee/fee-calculator.js.map +1 -0
- package/dist/fee/fee-ledger.d.ts +66 -0
- package/dist/fee/fee-ledger.d.ts.map +1 -0
- package/dist/fee/fee-ledger.js +181 -0
- package/dist/fee/fee-ledger.js.map +1 -0
- package/dist/gateway/config-loader.d.ts +38 -0
- package/dist/gateway/config-loader.d.ts.map +1 -0
- package/dist/gateway/config-loader.js +125 -0
- package/dist/gateway/config-loader.js.map +1 -0
- package/dist/gateway/server-registry.d.ts +25 -0
- package/dist/gateway/server-registry.d.ts.map +1 -0
- package/dist/gateway/server-registry.js +59 -0
- package/dist/gateway/server-registry.js.map +1 -0
- package/dist/gateway/toolpay-gateway.d.ts +57 -0
- package/dist/gateway/toolpay-gateway.d.ts.map +1 -0
- package/dist/gateway/toolpay-gateway.js +320 -0
- package/dist/gateway/toolpay-gateway.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk/client-agent.d.ts +47 -0
- package/dist/sdk/client-agent.d.ts.map +1 -0
- package/dist/sdk/client-agent.js +158 -0
- package/dist/sdk/client-agent.js.map +1 -0
- package/dist/sdk/server-sdk.d.ts +51 -0
- package/dist/sdk/server-sdk.d.ts.map +1 -0
- package/dist/sdk/server-sdk.js +170 -0
- package/dist/sdk/server-sdk.js.map +1 -0
- package/dist/settlement/base-contract-client.d.ts +156 -0
- package/dist/settlement/base-contract-client.d.ts.map +1 -0
- package/dist/settlement/base-contract-client.js +156 -0
- package/dist/settlement/base-contract-client.js.map +1 -0
- package/dist/settlement/nonce-store.d.ts +54 -0
- package/dist/settlement/nonce-store.d.ts.map +1 -0
- package/dist/settlement/nonce-store.js +125 -0
- package/dist/settlement/nonce-store.js.map +1 -0
- package/dist/settlement/onchain-verifier.d.ts +41 -0
- package/dist/settlement/onchain-verifier.d.ts.map +1 -0
- package/dist/settlement/onchain-verifier.js +114 -0
- package/dist/settlement/onchain-verifier.js.map +1 -0
- package/dist/settlement/payment-orchestrator.d.ts +59 -0
- package/dist/settlement/payment-orchestrator.d.ts.map +1 -0
- package/dist/settlement/payment-orchestrator.js +282 -0
- package/dist/settlement/payment-orchestrator.js.map +1 -0
- package/dist/settlement/permit-verifier.d.ts +60 -0
- package/dist/settlement/permit-verifier.d.ts.map +1 -0
- package/dist/settlement/permit-verifier.js +80 -0
- package/dist/settlement/permit-verifier.js.map +1 -0
- package/dist/settlement/session-wallet.d.ts +60 -0
- package/dist/settlement/session-wallet.d.ts.map +1 -0
- package/dist/settlement/session-wallet.js +105 -0
- package/dist/settlement/session-wallet.js.map +1 -0
- package/dist/transports/sse-transport.d.ts +27 -0
- package/dist/transports/sse-transport.d.ts.map +1 -0
- package/dist/transports/sse-transport.js +65 -0
- package/dist/transports/sse-transport.js.map +1 -0
- package/dist/transports/stdio-bridge.d.ts +29 -0
- package/dist/transports/stdio-bridge.d.ts.map +1 -0
- package/dist/transports/stdio-bridge.js +133 -0
- package/dist/transports/stdio-bridge.js.map +1 -0
- package/dist/types/circuit-breaker.d.ts +22 -0
- package/dist/types/circuit-breaker.d.ts.map +1 -0
- package/dist/types/circuit-breaker.js +5 -0
- package/dist/types/circuit-breaker.js.map +1 -0
- package/dist/types/ledger.d.ts +57 -0
- package/dist/types/ledger.d.ts.map +1 -0
- package/dist/types/ledger.js +5 -0
- package/dist/types/ledger.js.map +1 -0
- package/dist/types/mcp.d.ts +67 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +5 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/pricing.d.ts +68 -0
- package/dist/types/pricing.d.ts.map +1 -0
- package/dist/types/pricing.js +5 -0
- package/dist/types/pricing.js.map +1 -0
- package/dist/types/x402.d.ts +76 -0
- package/dist/types/x402.d.ts.map +1 -0
- package/dist/types/x402.js +5 -0
- package/dist/types/x402.js.map +1 -0
- package/dist/utils/units.d.ts +45 -0
- package/dist/utils/units.d.ts.map +1 -0
- package/dist/utils/units.js +141 -0
- package/dist/utils/units.js.map +1 -0
- package/package.json +72 -0
- package/src/contracts/ToolPayRouter.json +371 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1560 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ToolPay CLI
|
|
4
|
+
* Command-line interface for MCP developers to configure, validate, and launch ToolPay gateways.
|
|
5
|
+
*/
|
|
6
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, realpathSync } from 'node:fs';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { resolve, join } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { createInterface } from 'node:readline';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { createServer } from 'node:http';
|
|
13
|
+
import { loadConfigFile, generateSampleConfigFile } from './gateway/config-loader.js';
|
|
14
|
+
import { ServerRegistry } from './gateway/server-registry.js';
|
|
15
|
+
import { ToolPayGateway } from './gateway/toolpay-gateway.js';
|
|
16
|
+
import { PaymentOrchestrator } from './settlement/payment-orchestrator.js';
|
|
17
|
+
import { NonceStore } from './settlement/nonce-store.js';
|
|
18
|
+
import { PermitVerifier } from './settlement/permit-verifier.js';
|
|
19
|
+
import { OnChainVerifier } from './settlement/onchain-verifier.js';
|
|
20
|
+
import { SessionWalletManager } from './settlement/session-wallet.js';
|
|
21
|
+
import { FeeCalculator } from './fee/fee-calculator.js';
|
|
22
|
+
import { FeeLedger } from './fee/fee-ledger.js';
|
|
23
|
+
import { CircuitBreaker } from './circuit-breaker/circuit-breaker.js';
|
|
24
|
+
import { StdioBridge } from './transports/stdio-bridge.js';
|
|
25
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
26
|
+
function loadEnvFile() {
|
|
27
|
+
let dir = process.cwd();
|
|
28
|
+
for (let i = 0; i < 4; i++) {
|
|
29
|
+
const envPath = resolve(dir, '.env');
|
|
30
|
+
if (existsSync(envPath)) {
|
|
31
|
+
const lines = readFileSync(envPath, 'utf-8').split('\n');
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
35
|
+
continue;
|
|
36
|
+
const eqIdx = trimmed.indexOf('=');
|
|
37
|
+
if (eqIdx !== -1) {
|
|
38
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
39
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
40
|
+
if (!process.env[key]) {
|
|
41
|
+
process.env[key] = val;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
const parent = resolve(dir, '..');
|
|
48
|
+
if (parent === dir)
|
|
49
|
+
break;
|
|
50
|
+
dir = parent;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
loadEnvFile();
|
|
54
|
+
export function getCredentialsPath() {
|
|
55
|
+
const dir = join(homedir(), '.mcpaid');
|
|
56
|
+
if (!existsSync(dir)) {
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
return join(dir, 'credentials.json');
|
|
60
|
+
}
|
|
61
|
+
export function loadCredentials() {
|
|
62
|
+
try {
|
|
63
|
+
const p = getCredentialsPath();
|
|
64
|
+
if (existsSync(p)) {
|
|
65
|
+
const data = JSON.parse(readFileSync(p, 'utf-8'));
|
|
66
|
+
if (data && data.token)
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
export function saveCredentials(creds) {
|
|
74
|
+
const p = getCredentialsPath();
|
|
75
|
+
writeFileSync(p, JSON.stringify(creds, null, 2), 'utf-8');
|
|
76
|
+
}
|
|
77
|
+
export function clearCredentials() {
|
|
78
|
+
try {
|
|
79
|
+
const p = getCredentialsPath();
|
|
80
|
+
if (existsSync(p)) {
|
|
81
|
+
unlinkSync(p);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
85
|
+
}
|
|
86
|
+
export function getFlagValue(flags, name, alias) {
|
|
87
|
+
for (let i = 0; i < flags.length; i++) {
|
|
88
|
+
const f = flags[i];
|
|
89
|
+
if (f === `--${name}` || (alias && f === `-${alias}`)) {
|
|
90
|
+
return flags[i + 1];
|
|
91
|
+
}
|
|
92
|
+
if (f.startsWith(`--${name}=`)) {
|
|
93
|
+
return f.slice(name.length + 3);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
export function hasFlag(flags, name, alias) {
|
|
99
|
+
return flags.some((f) => f === `--${name}` || (alias && f === `-${alias}`) || f.startsWith(`--${name}=`));
|
|
100
|
+
}
|
|
101
|
+
export function promptQuestion(query) {
|
|
102
|
+
const rl = createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout,
|
|
105
|
+
});
|
|
106
|
+
return new Promise((res) => {
|
|
107
|
+
rl.question(query, (ans) => {
|
|
108
|
+
rl.close();
|
|
109
|
+
res(ans.trim());
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function printHelp() {
|
|
114
|
+
console.log(`
|
|
115
|
+
MCPaid CLI - Zero-Friction MCP Monetization Gateway
|
|
116
|
+
|
|
117
|
+
Commands:
|
|
118
|
+
init Generate a sample mcpaid.config.json in the current directory
|
|
119
|
+
validate [config-path] Validate an mcpaid.config.json or toolpay.config.json file
|
|
120
|
+
start [config-path] Start local MCPaid gateway proxy on port 8080 (or PORT env)
|
|
121
|
+
dev [config-path] Run live zero-config development tunnel with security shield
|
|
122
|
+
bridge --gateway <url> Run stdio bridge piping local MCP desktop calls to MCPaid
|
|
123
|
+
agent-proxy --url <url> Connect local MCP clients / AI agents to remote monetized gateway (alias for bridge)
|
|
124
|
+
publish [config-path] Publish/deploy an MCP server to MCPaid Edge Gateway
|
|
125
|
+
tunnel [port] Launch zero-config Cloudflare tunnel for local MCP (default: 3000)
|
|
126
|
+
server add [config-path] Register an MCP server on MCPaid Edge Gateway
|
|
127
|
+
server list List your registered MCP servers
|
|
128
|
+
server remove <id> Disconnect and remove an MCP server from MCPaid
|
|
129
|
+
login Sign in to your MCPaid developer account
|
|
130
|
+
logout Log out and remove local credentials
|
|
131
|
+
whoami Show currently authenticated developer account
|
|
132
|
+
withdraw [amount] Withdraw available USDC earnings to your Base L2 payout wallet
|
|
133
|
+
withdrawals View payout and withdrawal history and live transfer statuses
|
|
134
|
+
relayer [options] Inspect automated Base L2 settlement status (operator --run gated)
|
|
135
|
+
wallet new Generate a fresh EVM wallet with funding instructions
|
|
136
|
+
--developer, -d Generate developer payout wallet for receiving earnings
|
|
137
|
+
--agent, -a Generate autonomous agent spending wallet for tool calls
|
|
138
|
+
wallet status Inspect configured agent wallet address and network settings
|
|
139
|
+
|
|
140
|
+
Options:
|
|
141
|
+
--port, -p <number> Port to listen on (default: 8080)
|
|
142
|
+
--gateway <url> Gateway URL (default: https://mcpaid.dev)
|
|
143
|
+
--upstream <url> Override upstream URL when publishing (e.g. tunnel URL)
|
|
144
|
+
--help, -h Show this help message
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
async function handleInit() {
|
|
148
|
+
const targetPath = resolve(process.cwd(), 'mcpaid.config.json');
|
|
149
|
+
if (existsSync(targetPath)) {
|
|
150
|
+
console.error(`Error: ${targetPath} already exists.`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const sample = generateSampleConfigFile();
|
|
154
|
+
writeFileSync(targetPath, sample, 'utf-8');
|
|
155
|
+
console.log(`✅ Created sample MCPaid configuration at: ${targetPath}`);
|
|
156
|
+
console.log(`\nNext steps:`);
|
|
157
|
+
console.log(`1. Open mcpaid.config.json and set your payoutWallet (Base/EVM address).`);
|
|
158
|
+
console.log(`2. Define your free vs paid tools.`);
|
|
159
|
+
console.log(`3. Run: npx @mcpaid/sdk start mcpaid.config.json\n`);
|
|
160
|
+
}
|
|
161
|
+
async function handleValidate(configPath) {
|
|
162
|
+
try {
|
|
163
|
+
const defaultPath = existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
|
|
164
|
+
? 'mcpaid.config.json'
|
|
165
|
+
: 'toolpay.config.json';
|
|
166
|
+
const fullPath = resolve(process.cwd(), configPath || defaultPath);
|
|
167
|
+
const { config, upstreamUrl } = loadConfigFile(fullPath);
|
|
168
|
+
console.log(`✅ Configuration valid!`);
|
|
169
|
+
console.log(`- Server ID: ${config.serverId}`);
|
|
170
|
+
console.log(`- Upstream Target: ${upstreamUrl}`);
|
|
171
|
+
console.log(`- Payout Wallet: ${config.payoutWallet}`);
|
|
172
|
+
console.log(`- Tools Configured: ${config.tools.length}`);
|
|
173
|
+
for (const tool of config.tools) {
|
|
174
|
+
const pricingStr = tool.type === 'free' ? 'FREE' : `$${tool.priceUsd} / call`;
|
|
175
|
+
console.log(` • ${tool.toolName.padEnd(25)} -> ${pricingStr}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.error(`❌ Validation failed: ${err.message}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function handleStart(configPath) {
|
|
184
|
+
const defaultPath = existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
|
|
185
|
+
? 'mcpaid.config.json'
|
|
186
|
+
: 'toolpay.config.json';
|
|
187
|
+
const fullPath = resolve(process.cwd(), configPath || defaultPath);
|
|
188
|
+
const { config, upstreamUrl } = loadConfigFile(fullPath);
|
|
189
|
+
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080;
|
|
190
|
+
const registry = new ServerRegistry();
|
|
191
|
+
registry.registerServer(config);
|
|
192
|
+
const treasuryWallet = config.platformFee?.platformTreasuryWallet || '0x1001001001001001001001001001001001001001';
|
|
193
|
+
const feeLedger = new FeeLedger(treasuryWallet);
|
|
194
|
+
const feeCalculator = new FeeCalculator(treasuryWallet, config.platformFee?.platformFeeBasisPoints ?? 300);
|
|
195
|
+
const orchestrator = new PaymentOrchestrator({
|
|
196
|
+
nonceStore: new NonceStore(),
|
|
197
|
+
permitVerifier: new PermitVerifier(),
|
|
198
|
+
onChainVerifier: new OnChainVerifier({ getTransaction: async () => null }),
|
|
199
|
+
sessionWalletManager: new SessionWalletManager(),
|
|
200
|
+
feeCalculator,
|
|
201
|
+
feeLedger,
|
|
202
|
+
});
|
|
203
|
+
const circuitBreaker = new CircuitBreaker();
|
|
204
|
+
// Forwarding handler to upstream MCP HTTP endpoint
|
|
205
|
+
const upstreamHandler = async (req) => {
|
|
206
|
+
try {
|
|
207
|
+
const res = await fetch(upstreamUrl, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: JSON.stringify(req),
|
|
211
|
+
});
|
|
212
|
+
return (await res.json());
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
return {
|
|
216
|
+
jsonrpc: '2.0',
|
|
217
|
+
id: req.id ?? null,
|
|
218
|
+
error: { code: -32000, message: `Upstream error at ${upstreamUrl}: ${err.message}` },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const gateway = new ToolPayGateway({
|
|
223
|
+
registry,
|
|
224
|
+
orchestrator,
|
|
225
|
+
circuitBreaker,
|
|
226
|
+
upstreamHandler,
|
|
227
|
+
});
|
|
228
|
+
const server = createServer(async (req, res) => {
|
|
229
|
+
if (req.method !== 'POST') {
|
|
230
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
231
|
+
res.end('Method Not Allowed. ToolPay accepts MCP JSON-RPC POST requests.');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let bodyRaw = '';
|
|
235
|
+
req.on('data', (chunk) => {
|
|
236
|
+
bodyRaw += chunk;
|
|
237
|
+
});
|
|
238
|
+
req.on('end', async () => {
|
|
239
|
+
try {
|
|
240
|
+
const jsonRpcReq = JSON.parse(bodyRaw);
|
|
241
|
+
// Extract payment authorization headers if present
|
|
242
|
+
let paymentProof;
|
|
243
|
+
const authHeader = req.headers['authorization'] || req.headers['x-payment-proof'];
|
|
244
|
+
if (typeof authHeader === 'string') {
|
|
245
|
+
const raw = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
246
|
+
if (raw.startsWith('{')) {
|
|
247
|
+
try {
|
|
248
|
+
paymentProof = JSON.parse(raw);
|
|
249
|
+
}
|
|
250
|
+
catch { }
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
try {
|
|
254
|
+
const decoded = Buffer.from(raw, 'base64').toString('utf8');
|
|
255
|
+
if (decoded.startsWith('{')) {
|
|
256
|
+
paymentProof = JSON.parse(decoded);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch { }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const agentWallet = req.headers['x-agent-wallet'] || paymentProof?.signerWallet;
|
|
263
|
+
const idempotencyKey = req.headers['idempotency-key'];
|
|
264
|
+
const gatewayRes = await gateway.handleRequest(jsonRpcReq, {
|
|
265
|
+
serverId: config.serverId,
|
|
266
|
+
agentWallet,
|
|
267
|
+
paymentProof,
|
|
268
|
+
idempotencyKey,
|
|
269
|
+
});
|
|
270
|
+
res.writeHead(gatewayRes.statusCode, gatewayRes.headers);
|
|
271
|
+
res.end(JSON.stringify(gatewayRes.body));
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
275
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
server.listen(port, () => {
|
|
280
|
+
console.log(`\n======================================================`);
|
|
281
|
+
console.log(`🚀 MCPaid Gateway running on http://localhost:${port}`);
|
|
282
|
+
console.log(`• Monitored Server: ${config.serverId} (${config.serverName})`);
|
|
283
|
+
console.log(`• Upstream Target: ${upstreamUrl}`);
|
|
284
|
+
console.log(`• Payout Wallet: ${config.payoutWallet}`);
|
|
285
|
+
console.log(`• Settlement Rail: ${config.network.toUpperCase()} (${config.currency})`);
|
|
286
|
+
console.log(`• Platform Cut: ${(config.platformFee?.platformFeeBasisPoints ?? 300) / 100}%`);
|
|
287
|
+
console.log(`======================================================\n`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async function handleBridge() {
|
|
291
|
+
let gatewayUrl;
|
|
292
|
+
const gwIdx = process.argv.indexOf('--gateway');
|
|
293
|
+
const urlIdx = process.argv.indexOf('--url');
|
|
294
|
+
if (gwIdx !== -1 && process.argv[gwIdx + 1]) {
|
|
295
|
+
gatewayUrl = process.argv[gwIdx + 1];
|
|
296
|
+
}
|
|
297
|
+
else if (urlIdx !== -1 && process.argv[urlIdx + 1]) {
|
|
298
|
+
gatewayUrl = process.argv[urlIdx + 1];
|
|
299
|
+
}
|
|
300
|
+
else if (process.argv[3] && !process.argv[3].startsWith('--')) {
|
|
301
|
+
gatewayUrl = process.argv[3];
|
|
302
|
+
}
|
|
303
|
+
if (!gatewayUrl) {
|
|
304
|
+
console.error('Error: Missing gateway URL. Usage: mcpaid bridge --gateway <url> (or --url <url>)');
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const walletIndex = process.argv.indexOf('--wallet');
|
|
308
|
+
const agentWallet = walletIndex !== -1 ? process.argv[walletIndex + 1] : undefined;
|
|
309
|
+
const noWallet = process.argv.includes('--no-wallet');
|
|
310
|
+
const keyIndex = process.argv.indexOf('--key');
|
|
311
|
+
const privateKey = noWallet
|
|
312
|
+
? undefined
|
|
313
|
+
: (keyIndex !== -1
|
|
314
|
+
? process.argv[keyIndex + 1]
|
|
315
|
+
: (process.env.AGENT_PRIVATE_KEY || process.env.DEPLOYER_PRIVATE_KEY));
|
|
316
|
+
const maxPriceIndex = process.argv.indexOf('--max-price');
|
|
317
|
+
const autoApproveMaxUsd = maxPriceIndex !== -1
|
|
318
|
+
? parseFloat(process.argv[maxPriceIndex + 1])
|
|
319
|
+
: (process.env.AGENT_MAX_PRICE_USD ? parseFloat(process.env.AGENT_MAX_PRICE_USD) : 1.00);
|
|
320
|
+
console.error(`[MCPaid Stdio Bridge] Connected to gateway: ${gatewayUrl}`);
|
|
321
|
+
if (privateKey) {
|
|
322
|
+
console.error(`[MCPaid Stdio Bridge] Autonomous wallet payment enabled (Auto-approve limit: $${autoApproveMaxUsd}/call)`);
|
|
323
|
+
}
|
|
324
|
+
const bridge = new StdioBridge({
|
|
325
|
+
gatewayUrl,
|
|
326
|
+
agentWallet,
|
|
327
|
+
privateKey,
|
|
328
|
+
autoApproveMaxUsd,
|
|
329
|
+
});
|
|
330
|
+
bridge.start();
|
|
331
|
+
}
|
|
332
|
+
async function handleWallet(subcommand) {
|
|
333
|
+
const walletArgs = process.argv.slice(3);
|
|
334
|
+
const isNew = !subcommand || subcommand === 'new' || subcommand === 'create' || subcommand === 'generate' || subcommand === 'developer' || subcommand === 'agent';
|
|
335
|
+
const isDeveloper = walletArgs.some((a) => a === '--developer' || a === '-d' || a === 'developer');
|
|
336
|
+
const isAgent = walletArgs.some((a) => a === '--agent' || a === '-a' || a === 'agent');
|
|
337
|
+
if (isNew && subcommand !== 'status' && subcommand !== 'info') {
|
|
338
|
+
const privateKey = generatePrivateKey();
|
|
339
|
+
const account = privateKeyToAccount(privateKey);
|
|
340
|
+
if (isDeveloper) {
|
|
341
|
+
console.log(`
|
|
342
|
+
======================================================
|
|
343
|
+
🔑 New MCPaid Developer Payout Wallet Generated
|
|
344
|
+
======================================================
|
|
345
|
+
• Payout Address: ${account.address}
|
|
346
|
+
• Private Key: ${privateKey}
|
|
347
|
+
======================================================
|
|
348
|
+
|
|
349
|
+
📋 Developer Setup Instructions:
|
|
350
|
+
1. Payout Wallet Address (Base L2):
|
|
351
|
+
${account.address}
|
|
352
|
+
|
|
353
|
+
2. Configure Your Server:
|
|
354
|
+
• In mcpaid.config.json, set:
|
|
355
|
+
"payoutWallet": "${account.address}"
|
|
356
|
+
• Or paste into the MCPaid Developer Dashboard:
|
|
357
|
+
https://mcpaid.dev/dashboard
|
|
358
|
+
• Or publish via CLI:
|
|
359
|
+
npx @mcpaid/sdk publish
|
|
360
|
+
|
|
361
|
+
3. On-Chain Revenue Settlement:
|
|
362
|
+
• 97% net developer revenue from all paid tool calls settles
|
|
363
|
+
directly to this address on Base L2 in Native USDC.
|
|
364
|
+
• Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
|
365
|
+
|
|
366
|
+
4. Accessing & Withdrawing Funds:
|
|
367
|
+
• Import this private key into Coinbase Wallet, MetaMask, or Rabby.
|
|
368
|
+
• View your address on Basescan:
|
|
369
|
+
https://basescan.org/address/${account.address}
|
|
370
|
+
======================================================
|
|
371
|
+
`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (isAgent) {
|
|
375
|
+
console.log(`
|
|
376
|
+
======================================================
|
|
377
|
+
🔑 New MCPaid Autonomous Agent Wallet Generated
|
|
378
|
+
======================================================
|
|
379
|
+
• Agent Address: ${account.address}
|
|
380
|
+
• Private Key: ${privateKey}
|
|
381
|
+
======================================================
|
|
382
|
+
|
|
383
|
+
📋 Quick Setup:
|
|
384
|
+
1. Add this key to your agent project's .env file:
|
|
385
|
+
AGENT_PRIVATE_KEY=${privateKey}
|
|
386
|
+
|
|
387
|
+
2. Funding on Base L2 (USDC):
|
|
388
|
+
• Production (Base Mainnet - Chain ID 8453):
|
|
389
|
+
- Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
|
390
|
+
- Coinbase Direct: Withdraw USDC directly to ${account.address} selecting the "Base" network (<$0.01 gas fee).
|
|
391
|
+
- Official Base Bridge: Bridge USDC/ETH from Ethereum at https://bridge.base.org.
|
|
392
|
+
- DEX Swap: Swap ETH/tokens to USDC on Aerodrome or Uniswap on Base.
|
|
393
|
+
• Staging / Testnet (Base Sepolia - Chain ID 84532):
|
|
394
|
+
- Free USDC: https://faucet.circle.com (select Base Sepolia, paste ${account.address})
|
|
395
|
+
- Free Gas: https://console.optimism.io/faucet
|
|
396
|
+
|
|
397
|
+
3. Using Paid MCP Tools:
|
|
398
|
+
• In your MCP client configuration (Claude Desktop, Cursor, OpenCode), configure the bridge:
|
|
399
|
+
npx @mcpaid/sdk bridge --gateway <gateway-url>
|
|
400
|
+
• The bridge automatically signs zero-gas EIP-712 permits using your AGENT_PRIVATE_KEY
|
|
401
|
+
and executes paid tools with zero manual approval friction.
|
|
402
|
+
======================================================
|
|
403
|
+
`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
// Default: Display wallet with sections for both roles
|
|
407
|
+
console.log(`
|
|
408
|
+
======================================================
|
|
409
|
+
🔑 New MCPaid EVM Keypair Generated
|
|
410
|
+
======================================================
|
|
411
|
+
• Public Address: ${account.address}
|
|
412
|
+
• Private Key: ${privateKey}
|
|
413
|
+
======================================================
|
|
414
|
+
|
|
415
|
+
💡 Tip: Run with --developer or --agent for role-specific guidance:
|
|
416
|
+
npx @mcpaid/sdk wallet new --developer
|
|
417
|
+
npx @mcpaid/sdk wallet new --agent
|
|
418
|
+
|
|
419
|
+
──────────────────────────────────────────────────────
|
|
420
|
+
[ROLE 1: DEVELOPER PAYOUT WALLET]
|
|
421
|
+
──────────────────────────────────────────────────────
|
|
422
|
+
• Set "payoutWallet": "${account.address}" in mcpaid.config.json or in the dashboard (https://mcpaid.dev/dashboard).
|
|
423
|
+
• All 97% developer payouts from agent tool calls settle directly to this Base L2 address in native USDC.
|
|
424
|
+
• Basescan: https://basescan.org/address/${account.address}
|
|
425
|
+
|
|
426
|
+
──────────────────────────────────────────────────────
|
|
427
|
+
[ROLE 2: AI AGENT SPENDING WALLET]
|
|
428
|
+
──────────────────────────────────────────────────────
|
|
429
|
+
• Set AGENT_PRIVATE_KEY=${privateKey} in your project's .env file.
|
|
430
|
+
• Fund with small USDC on Base (via Coinbase Direct or https://bridge.base.org).
|
|
431
|
+
• Connect via stdio bridge: npx @mcpaid/sdk bridge --gateway <gateway-url>
|
|
432
|
+
======================================================
|
|
433
|
+
`);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (subcommand === 'status' || subcommand === 'info') {
|
|
437
|
+
const creds = loadCredentials();
|
|
438
|
+
const key = process.env.AGENT_PRIVATE_KEY || process.env.DEPLOYER_PRIVATE_KEY;
|
|
439
|
+
if (!key && !creds?.payoutWallet) {
|
|
440
|
+
console.log(`
|
|
441
|
+
❌ No agent or developer wallet configured in environment.
|
|
442
|
+
Run:
|
|
443
|
+
• For Developers: npx @mcpaid/sdk wallet new --developer
|
|
444
|
+
• For AI Agents: npx @mcpaid/sdk wallet new --agent
|
|
445
|
+
`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
console.log(`
|
|
449
|
+
======================================================
|
|
450
|
+
👛 Configured MCPaid Wallet Status
|
|
451
|
+
======================================================`);
|
|
452
|
+
if (key) {
|
|
453
|
+
try {
|
|
454
|
+
const account = privateKeyToAccount(key.startsWith('0x') ? key : `0x${key}`);
|
|
455
|
+
console.log(`• Agent / Local Wallet: ${account.address}`);
|
|
456
|
+
console.log(`• Config Source: ${process.env.AGENT_PRIVATE_KEY ? 'AGENT_PRIVATE_KEY' : 'DEPLOYER_PRIVATE_KEY'}`);
|
|
457
|
+
}
|
|
458
|
+
catch (e) {
|
|
459
|
+
console.error(`Invalid private key in environment: ${e.message}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (creds?.payoutWallet) {
|
|
463
|
+
console.log(`• Account Payout Wallet: ${creds.payoutWallet}`);
|
|
464
|
+
}
|
|
465
|
+
console.log(`• Supported Rails: Base Mainnet (8453) & Base Sepolia (84532)
|
|
466
|
+
• Native USDC Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
|
467
|
+
======================================================
|
|
468
|
+
`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
console.log(`
|
|
472
|
+
Usage: npx @mcpaid/sdk wallet <command> [options]
|
|
473
|
+
|
|
474
|
+
Commands:
|
|
475
|
+
new [--developer|--agent] Generate a fresh EVM wallet with tailored instructions
|
|
476
|
+
status Show the public address and settings for configured wallet
|
|
477
|
+
`);
|
|
478
|
+
}
|
|
479
|
+
async function handleLogin() {
|
|
480
|
+
const args = process.argv.slice(3);
|
|
481
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
482
|
+
let email = getFlagValue(args, 'email');
|
|
483
|
+
if (!email) {
|
|
484
|
+
email = await promptQuestion('Enter your developer email: ');
|
|
485
|
+
}
|
|
486
|
+
email = email.toLowerCase().trim();
|
|
487
|
+
if (!email || !email.includes('@')) {
|
|
488
|
+
console.error('❌ Error: A valid email address is required.');
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
console.log(`\nConnecting to MCPaid Edge Gateway (${gatewayUrl})...`);
|
|
492
|
+
let authPurpose = 'login_2fa';
|
|
493
|
+
let loginRes = await fetch(`${gatewayUrl}/v1/auth/login`, {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
headers: { 'Content-Type': 'application/json' },
|
|
496
|
+
body: JSON.stringify({ email }),
|
|
497
|
+
});
|
|
498
|
+
let loginData = (await loginRes.json().catch(() => ({})));
|
|
499
|
+
if (loginRes.status === 404) {
|
|
500
|
+
authPurpose = 'signup_confirmation';
|
|
501
|
+
console.log(`No existing account found for ${email}. Initiating developer account signup...`);
|
|
502
|
+
const signupRes = await fetch(`${gatewayUrl}/v1/auth/signup`, {
|
|
503
|
+
method: 'POST',
|
|
504
|
+
headers: { 'Content-Type': 'application/json' },
|
|
505
|
+
body: JSON.stringify({ email }),
|
|
506
|
+
});
|
|
507
|
+
loginData = (await signupRes.json().catch(() => ({})));
|
|
508
|
+
if (!signupRes.ok) {
|
|
509
|
+
console.error(`❌ Signup failed: ${loginData.error || JSON.stringify(loginData)}`);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else if (!loginRes.ok) {
|
|
514
|
+
console.error(`❌ Login request failed: ${loginData.error || JSON.stringify(loginData)}`);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
console.log(`\n📧 Verification code sent to ${email}. Check your inbox.`);
|
|
518
|
+
if (loginData.devCodeHint) {
|
|
519
|
+
console.log(`[Dev Fallback Code]: ${loginData.devCodeHint}`);
|
|
520
|
+
}
|
|
521
|
+
let code = getFlagValue(args, 'code');
|
|
522
|
+
if (!code) {
|
|
523
|
+
code = await promptQuestion('Enter 6-digit verification code: ');
|
|
524
|
+
}
|
|
525
|
+
code = code.trim();
|
|
526
|
+
const verifyRes = await fetch(`${gatewayUrl}/v1/auth/verify`, {
|
|
527
|
+
method: 'POST',
|
|
528
|
+
headers: { 'Content-Type': 'application/json' },
|
|
529
|
+
body: JSON.stringify({ email, code, purpose: authPurpose, rememberMe: true }),
|
|
530
|
+
});
|
|
531
|
+
const verifyData = (await verifyRes.json().catch(() => ({})));
|
|
532
|
+
if (!verifyRes.ok || !verifyData.success) {
|
|
533
|
+
console.error(`❌ Verification failed: ${verifyData.error || 'Invalid or expired code'}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
saveCredentials({
|
|
537
|
+
email: verifyData.user.email,
|
|
538
|
+
token: verifyData.token,
|
|
539
|
+
gatewayUrl,
|
|
540
|
+
payoutWallet: verifyData.user.payout_wallet || undefined,
|
|
541
|
+
});
|
|
542
|
+
console.log(`
|
|
543
|
+
======================================================
|
|
544
|
+
✅ Successfully Authenticated with MCPaid!
|
|
545
|
+
======================================================
|
|
546
|
+
• Developer Email: ${verifyData.user.email}
|
|
547
|
+
• Payout Wallet: ${verifyData.user.payout_wallet || 'None configured yet (set on publish or dashboard)'}
|
|
548
|
+
• Gateway Target: ${gatewayUrl}
|
|
549
|
+
• Session Saved: ${getCredentialsPath()}
|
|
550
|
+
======================================================
|
|
551
|
+
`);
|
|
552
|
+
}
|
|
553
|
+
async function handleLogout() {
|
|
554
|
+
const creds = loadCredentials();
|
|
555
|
+
if (creds) {
|
|
556
|
+
try {
|
|
557
|
+
await fetch(`${creds.gatewayUrl}/v1/auth/logout`, {
|
|
558
|
+
method: 'POST',
|
|
559
|
+
headers: {
|
|
560
|
+
'Content-Type': 'application/json',
|
|
561
|
+
Authorization: `Bearer ${creds.token}`,
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
catch { }
|
|
566
|
+
}
|
|
567
|
+
clearCredentials();
|
|
568
|
+
console.log('✅ Successfully logged out and removed local credentials.');
|
|
569
|
+
}
|
|
570
|
+
async function handleWhoami() {
|
|
571
|
+
const creds = loadCredentials();
|
|
572
|
+
if (!creds) {
|
|
573
|
+
console.log('❌ Not logged in. Run "npx @mcpaid/sdk login" to sign in.');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const gatewayUrl = creds.gatewayUrl || 'https://mcpaid.dev';
|
|
577
|
+
try {
|
|
578
|
+
const res = await fetch(`${gatewayUrl}/v1/auth/me`, {
|
|
579
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
580
|
+
});
|
|
581
|
+
const data = (await res.json());
|
|
582
|
+
if (data.authenticated && data.user) {
|
|
583
|
+
console.log(`
|
|
584
|
+
======================================================
|
|
585
|
+
👤 MCPaid Authenticated Developer Profile
|
|
586
|
+
======================================================
|
|
587
|
+
• Email: ${data.user.email}
|
|
588
|
+
• User ID: ${data.user.id}
|
|
589
|
+
• Payout Wallet: ${data.user.payout_wallet || 'Not configured yet'}
|
|
590
|
+
• Verified: ${data.user.email_verified ? 'Yes' : 'No'}
|
|
591
|
+
• Gateway: ${gatewayUrl}
|
|
592
|
+
======================================================
|
|
593
|
+
`);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
catch { }
|
|
598
|
+
console.log(`\n⚠️ Session expired or invalid for ${creds.email}. Please run "npx @mcpaid/sdk login".\n`);
|
|
599
|
+
}
|
|
600
|
+
export function isLocalOrPrivateHost(urlStr) {
|
|
601
|
+
try {
|
|
602
|
+
const parsed = new URL(urlStr);
|
|
603
|
+
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
604
|
+
return (host === 'localhost' ||
|
|
605
|
+
host === '127.0.0.1' ||
|
|
606
|
+
host === '0.0.0.0' ||
|
|
607
|
+
host === '::1' ||
|
|
608
|
+
host === '169.254.169.254' ||
|
|
609
|
+
host.endsWith('.local') ||
|
|
610
|
+
host.endsWith('.internal') ||
|
|
611
|
+
host.startsWith('10.') ||
|
|
612
|
+
host.startsWith('192.168.') ||
|
|
613
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(host));
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function handleTunnel(portArg) {
|
|
620
|
+
const port = portArg || '3000';
|
|
621
|
+
console.log(`\n⚡ Starting zero-config Cloudflare Tunnel to http://127.0.0.1:${port}...`);
|
|
622
|
+
const child = spawn('cloudflared', ['tunnel', '--url', `http://127.0.0.1:${port}`], {
|
|
623
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
624
|
+
});
|
|
625
|
+
child.on('error', (err) => {
|
|
626
|
+
if (err.code === 'ENOENT') {
|
|
627
|
+
console.error(`\n❌ "cloudflared" is not installed on your system.`);
|
|
628
|
+
console.log(`Install it with:`);
|
|
629
|
+
console.log(` macOS: brew install cloudflared`);
|
|
630
|
+
console.log(` Linux: sudo apt-get install cloudflared`);
|
|
631
|
+
console.log(` Windows: winget install Cloudflare.cloudflared\n`);
|
|
632
|
+
console.log(`Alternatively, you can use ngrok: ngrok http ${port}\n`);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
console.error(`❌ Failed to start cloudflared: ${err.message}`);
|
|
636
|
+
}
|
|
637
|
+
process.exit(1);
|
|
638
|
+
});
|
|
639
|
+
let detectedUrl = null;
|
|
640
|
+
const onData = (data) => {
|
|
641
|
+
const text = data.toString();
|
|
642
|
+
const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
|
|
643
|
+
if (match && !detectedUrl) {
|
|
644
|
+
detectedUrl = match[0];
|
|
645
|
+
console.log(`
|
|
646
|
+
======================================================
|
|
647
|
+
🌐 Cloudflare Tunnel Online!
|
|
648
|
+
======================================================
|
|
649
|
+
• Public Tunnel: ${detectedUrl}
|
|
650
|
+
• Local Forward: http://127.0.0.1:${port}
|
|
651
|
+
• MCP Target: ${detectedUrl}/mcp
|
|
652
|
+
|
|
653
|
+
In another terminal, publish your MCP server to MCPaid Edge:
|
|
654
|
+
npx @mcpaid/sdk publish --upstream ${detectedUrl}/mcp
|
|
655
|
+
======================================================
|
|
656
|
+
Press Ctrl+C to disconnect tunnel.
|
|
657
|
+
`);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
child.stdout.on('data', onData);
|
|
661
|
+
child.stderr.on('data', onData);
|
|
662
|
+
process.on('SIGINT', () => {
|
|
663
|
+
console.log('\n🛑 Disconnecting tunnel...');
|
|
664
|
+
child.kill();
|
|
665
|
+
process.exit(0);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
async function handleDev(configPathArg) {
|
|
669
|
+
const args = process.argv.slice(3);
|
|
670
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
671
|
+
let creds = loadCredentials();
|
|
672
|
+
let effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
|
|
673
|
+
if (!effectiveToken) {
|
|
674
|
+
console.log(`\n🔒 Authentication required to start a live dev tunnel on MCPaid Edge Gateway.`);
|
|
675
|
+
const doLogin = await promptQuestion('Would you like to sign in now? (Y/n): ');
|
|
676
|
+
if (doLogin.toLowerCase() === 'n') {
|
|
677
|
+
console.log('Dev mode cancelled. Run "npx @mcpaid/sdk login" first.');
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
await handleLogin();
|
|
681
|
+
creds = loadCredentials();
|
|
682
|
+
effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
|
|
683
|
+
}
|
|
684
|
+
if (!effectiveToken) {
|
|
685
|
+
console.error('❌ Authentication failed. Please login first.');
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
const candidatePath = configPathArg && !configPathArg.startsWith('-')
|
|
689
|
+
? configPathArg
|
|
690
|
+
: (existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
|
|
691
|
+
? 'mcpaid.config.json'
|
|
692
|
+
: (existsSync(resolve(process.cwd(), 'toolpay.config.json')) ? 'toolpay.config.json' : null));
|
|
693
|
+
if (!candidatePath || !existsSync(resolve(process.cwd(), candidatePath))) {
|
|
694
|
+
console.error('❌ No mcpaid.config.json found in current directory. Run "npx @mcpaid/sdk init" first.');
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
const fullPath = resolve(process.cwd(), candidatePath);
|
|
698
|
+
console.log(`\n📄 Loading configuration from: ${candidatePath}`);
|
|
699
|
+
const loaded = loadConfigFile(fullPath);
|
|
700
|
+
const config = loaded.config;
|
|
701
|
+
const localUpstream = loaded.upstreamUrl;
|
|
702
|
+
// Validate payout wallet
|
|
703
|
+
if (!config.payoutWallet ||
|
|
704
|
+
config.payoutWallet.includes('YourBaseEVMWallet') ||
|
|
705
|
+
!/^0x[a-fA-F0-9]{40}$/.test(config.payoutWallet)) {
|
|
706
|
+
if (creds?.payoutWallet && /^0x[a-fA-F0-9]{40}$/.test(creds.payoutWallet)) {
|
|
707
|
+
console.log(`Using your account default payout wallet: ${creds.payoutWallet}`);
|
|
708
|
+
config.payoutWallet = creds.payoutWallet;
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
const wallet = await promptQuestion('Enter your Base EVM payout wallet address (0x...): ');
|
|
712
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
|
713
|
+
console.error('❌ Invalid Ethereum/Base address format (0x + 40 hex chars).');
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
config.payoutWallet = wallet;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Parse local upstream target
|
|
720
|
+
let localPort = 3000;
|
|
721
|
+
let localPath = '/mcp';
|
|
722
|
+
try {
|
|
723
|
+
const parsed = new URL(localUpstream);
|
|
724
|
+
if (parsed.port)
|
|
725
|
+
localPort = parseInt(parsed.port, 10);
|
|
726
|
+
if (parsed.pathname && parsed.pathname !== '/')
|
|
727
|
+
localPath = parsed.pathname;
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
console.error(`❌ Invalid upstreamUrl in config: ${localUpstream}`);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
console.log(`\n🛡️ Initializing local MCPaid Security Shield...`);
|
|
734
|
+
// Start Local Security Shield Proxy
|
|
735
|
+
// The shield verifies incoming requests have 'X-MCPaid-Gateway: true' to prevent direct tunnel bypass!
|
|
736
|
+
const shieldPort = localPort === 8089 ? 8091 : 8089;
|
|
737
|
+
let totalCalls = 0;
|
|
738
|
+
const shieldServer = createServer(async (req, res) => {
|
|
739
|
+
const isGateway = req.headers['x-mcpaid-gateway'] === 'true';
|
|
740
|
+
if (!isGateway) {
|
|
741
|
+
console.log(`\n⛔ [SECURITY SHIELD] Blocked direct unmonetized tunnel access from ${req.socket.remoteAddress || 'remote'}`);
|
|
742
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
743
|
+
res.end(JSON.stringify({
|
|
744
|
+
jsonrpc: '2.0',
|
|
745
|
+
error: {
|
|
746
|
+
code: -32001,
|
|
747
|
+
message: `Direct tunnel access prohibited. This MCP server is monetized and protected by MCPaid. Call via ${gatewayUrl}/mcp/${config.serverId}`,
|
|
748
|
+
},
|
|
749
|
+
}));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const chunks = [];
|
|
753
|
+
req.on('data', (c) => chunks.push(c));
|
|
754
|
+
req.on('end', async () => {
|
|
755
|
+
const bodyBuffer = Buffer.concat(chunks);
|
|
756
|
+
const startMs = Date.now();
|
|
757
|
+
let toolName = 'mcp-call';
|
|
758
|
+
try {
|
|
759
|
+
const json = JSON.parse(bodyBuffer.toString('utf-8'));
|
|
760
|
+
if (json.method === 'tools/call' && json.params?.name) {
|
|
761
|
+
toolName = json.params.name;
|
|
762
|
+
}
|
|
763
|
+
else if (json.method) {
|
|
764
|
+
toolName = json.method;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch { }
|
|
768
|
+
try {
|
|
769
|
+
const targetUrl = `http://127.0.0.1:${localPort}${req.url || localPath}`;
|
|
770
|
+
const upstreamResponse = await fetch(targetUrl, {
|
|
771
|
+
method: req.method || 'POST',
|
|
772
|
+
headers: {
|
|
773
|
+
'Content-Type': req.headers['content-type'] || 'application/json',
|
|
774
|
+
'X-Agent-Wallet': req.headers['x-agent-wallet'] || '',
|
|
775
|
+
'X-MCPaid-Edge': 'true',
|
|
776
|
+
},
|
|
777
|
+
body: bodyBuffer,
|
|
778
|
+
});
|
|
779
|
+
const respBody = await upstreamResponse.arrayBuffer();
|
|
780
|
+
const duration = Date.now() - startMs;
|
|
781
|
+
totalCalls++;
|
|
782
|
+
const agentWallet = req.headers['x-agent-wallet']
|
|
783
|
+
? String(req.headers['x-agent-wallet']).slice(0, 8) + '...'
|
|
784
|
+
: 'Agent';
|
|
785
|
+
console.log(`⚡ [CALL #${totalCalls}] ${toolName.padEnd(18)} | Agent: ${agentWallet} | Status: ${upstreamResponse.status} (${duration}ms)`);
|
|
786
|
+
res.writeHead(upstreamResponse.status, {
|
|
787
|
+
'Content-Type': upstreamResponse.headers.get('content-type') || 'application/json',
|
|
788
|
+
});
|
|
789
|
+
res.end(Buffer.from(respBody));
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
console.error(`❌ [LOCAL UPSTREAM ERROR] Could not connect to http://127.0.0.1:${localPort}: ${err.message}`);
|
|
793
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
794
|
+
res.end(JSON.stringify({
|
|
795
|
+
jsonrpc: '2.0',
|
|
796
|
+
error: {
|
|
797
|
+
code: -32000,
|
|
798
|
+
message: `Failed to connect to local MCP server at http://127.0.0.1:${localPort}: ${err.message}. Ensure your local server is running.`,
|
|
799
|
+
},
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
await new Promise((resolvePromise) => {
|
|
805
|
+
shieldServer.listen(shieldPort, '127.0.0.1', () => resolvePromise());
|
|
806
|
+
shieldServer.on('error', (err) => {
|
|
807
|
+
if (err.code === 'EADDRINUSE') {
|
|
808
|
+
shieldServer.listen(0, '127.0.0.1', () => resolvePromise());
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
const bound = shieldServer.address();
|
|
813
|
+
const actualShieldPort = typeof bound === 'object' && bound ? bound.port : shieldPort;
|
|
814
|
+
console.log(`🔒 Security Shield Active on 127.0.0.1:${actualShieldPort} (Direct bypasses blocked)`);
|
|
815
|
+
console.log(`⚡ Starting encrypted Cloudflare Tunnel...`);
|
|
816
|
+
const child = spawn('cloudflared', ['tunnel', '--url', `http://127.0.0.1:${actualShieldPort}`], {
|
|
817
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
818
|
+
});
|
|
819
|
+
child.on('error', (err) => {
|
|
820
|
+
if (err.code === 'ENOENT') {
|
|
821
|
+
console.error(`\n❌ "cloudflared" is not installed on your system.`);
|
|
822
|
+
console.log(`Install it with:`);
|
|
823
|
+
console.log(` macOS: brew install cloudflared`);
|
|
824
|
+
console.log(` Linux: sudo apt-get install cloudflared`);
|
|
825
|
+
console.log(` Windows: winget install Cloudflare.cloudflared\n`);
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
console.error(`❌ Failed to start cloudflared: ${err.message}`);
|
|
829
|
+
}
|
|
830
|
+
shieldServer.close();
|
|
831
|
+
process.exit(1);
|
|
832
|
+
});
|
|
833
|
+
let published = false;
|
|
834
|
+
const onData = async (data) => {
|
|
835
|
+
const text = data.toString();
|
|
836
|
+
const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i);
|
|
837
|
+
if (match && !published) {
|
|
838
|
+
published = true;
|
|
839
|
+
const tunnelBaseUrl = match[0];
|
|
840
|
+
const remoteUpstreamUrl = `${tunnelBaseUrl}${localPath}`;
|
|
841
|
+
console.log(`🌐 Cloudflare Tunnel Online: ${tunnelBaseUrl}`);
|
|
842
|
+
console.log(`🚀 Registering tunnel with MCPaid Edge Gateway (${gatewayUrl})...`);
|
|
843
|
+
try {
|
|
844
|
+
const res = await fetch(`${gatewayUrl}/v1/servers`, {
|
|
845
|
+
method: 'POST',
|
|
846
|
+
headers: {
|
|
847
|
+
'Content-Type': 'application/json',
|
|
848
|
+
Authorization: `Bearer ${effectiveToken}`,
|
|
849
|
+
},
|
|
850
|
+
body: JSON.stringify({ config, upstreamUrl: remoteUpstreamUrl }),
|
|
851
|
+
});
|
|
852
|
+
const pubData = (await res.json().catch(() => ({})));
|
|
853
|
+
if (!res.ok || !pubData.success) {
|
|
854
|
+
console.error(`❌ Failed to register tunnel on edge: ${pubData.error || JSON.stringify(pubData)}`);
|
|
855
|
+
child.kill();
|
|
856
|
+
shieldServer.close();
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
console.log(`
|
|
860
|
+
============================================================
|
|
861
|
+
⚡ MCPaid Live Development Tunnel (Connected & Protected)
|
|
862
|
+
============================================================
|
|
863
|
+
• Local MCP Target: http://127.0.0.1:${localPort}${localPath}
|
|
864
|
+
• Edge Gateway URL: ${gatewayUrl}/mcp/${config.serverId}
|
|
865
|
+
• Cloudflare Tunnel: ${tunnelBaseUrl} [TLS 1.3 Encrypted]
|
|
866
|
+
• Security Shield: ACTIVE (Direct tunnel bypasses blocked)
|
|
867
|
+
• Payout Wallet: ${config.payoutWallet} (Base L2)
|
|
868
|
+
• Monitored Tools (${config.tools.length}):
|
|
869
|
+
${config.tools.map((t) => ` • ${t.toolName.padEnd(20)} -> ${t.type === 'free' ? 'FREE' : `$${t.priceUsd} USDC`}`).join('\n')}
|
|
870
|
+
|
|
871
|
+
📋 Agent / Client Connection:
|
|
872
|
+
npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${config.serverId}
|
|
873
|
+
|
|
874
|
+
Ready! Agents can now invoke monetized tools. Press Ctrl+C to stop.
|
|
875
|
+
============================================================
|
|
876
|
+
`);
|
|
877
|
+
}
|
|
878
|
+
catch (err) {
|
|
879
|
+
console.error(`❌ Registration error: ${err.message}`);
|
|
880
|
+
child.kill();
|
|
881
|
+
shieldServer.close();
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
child.stdout.on('data', onData);
|
|
887
|
+
child.stderr.on('data', onData);
|
|
888
|
+
const cleanup = () => {
|
|
889
|
+
console.log('\n🛑 Disconnecting tunnel and local security shield...');
|
|
890
|
+
child.kill();
|
|
891
|
+
shieldServer.close();
|
|
892
|
+
process.exit(0);
|
|
893
|
+
};
|
|
894
|
+
process.on('SIGINT', cleanup);
|
|
895
|
+
process.on('SIGTERM', cleanup);
|
|
896
|
+
}
|
|
897
|
+
async function handlePublish(configPathArg) {
|
|
898
|
+
const args = process.argv.slice(3);
|
|
899
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
900
|
+
let creds = loadCredentials();
|
|
901
|
+
let effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
|
|
902
|
+
if (!effectiveToken) {
|
|
903
|
+
console.log(`\n🔒 Authentication required to publish servers to MCPaid Edge Gateway.`);
|
|
904
|
+
const doLogin = await promptQuestion('Would you like to sign in now? (Y/n): ');
|
|
905
|
+
if (doLogin.toLowerCase() === 'n') {
|
|
906
|
+
console.log('Publish cancelled. Run "npx @mcpaid/sdk login" when ready.');
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
await handleLogin();
|
|
910
|
+
creds = loadCredentials();
|
|
911
|
+
effectiveToken = creds?.token || process.env.MCPAID_TOKEN || process.env.TOOLPAY_ADMIN_KEY;
|
|
912
|
+
}
|
|
913
|
+
if (!effectiveToken) {
|
|
914
|
+
console.error('❌ Authentication failed. Please login first.');
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
let config;
|
|
918
|
+
let upstreamUrl;
|
|
919
|
+
const candidatePath = configPathArg && !configPathArg.startsWith('-')
|
|
920
|
+
? configPathArg
|
|
921
|
+
: (existsSync(resolve(process.cwd(), 'mcpaid.config.json'))
|
|
922
|
+
? 'mcpaid.config.json'
|
|
923
|
+
: (existsSync(resolve(process.cwd(), 'toolpay.config.json')) ? 'toolpay.config.json' : null));
|
|
924
|
+
if (candidatePath && existsSync(resolve(process.cwd(), candidatePath))) {
|
|
925
|
+
const fullPath = resolve(process.cwd(), candidatePath);
|
|
926
|
+
console.log(`\n📄 Loading configuration from: ${candidatePath}`);
|
|
927
|
+
const loaded = loadConfigFile(fullPath);
|
|
928
|
+
config = loaded.config;
|
|
929
|
+
upstreamUrl = loaded.upstreamUrl;
|
|
930
|
+
// Check if payout wallet is placeholder
|
|
931
|
+
if (!config.payoutWallet ||
|
|
932
|
+
config.payoutWallet.includes('YourBaseEVMWallet') ||
|
|
933
|
+
!/^0x[a-fA-F0-9]{40}$/.test(config.payoutWallet)) {
|
|
934
|
+
if (creds?.payoutWallet && /^0x[a-fA-F0-9]{40}$/.test(creds.payoutWallet)) {
|
|
935
|
+
console.log(`Using your account default payout wallet: ${creds.payoutWallet}`);
|
|
936
|
+
config.payoutWallet = creds.payoutWallet;
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
const wallet = await promptQuestion('Enter your Base EVM payout wallet address (0x...): ');
|
|
940
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
|
941
|
+
console.error('❌ Invalid Ethereum/Base address format (must be 0x followed by 40 hex chars).');
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
config.payoutWallet = wallet;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
// Interactive wizard
|
|
950
|
+
console.log(`\n⚡ No mcpaid.config.json found. Let's configure your MCP server interactively:\n`);
|
|
951
|
+
const serverId = await promptQuestion('Server slug ID (e.g. my-awesome-mcp): ');
|
|
952
|
+
if (!serverId) {
|
|
953
|
+
console.error('❌ Server ID slug is required.');
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
const serverName = (await promptQuestion('Friendly server name: ')) || serverId;
|
|
957
|
+
upstreamUrl = await promptQuestion('Upstream MCP URL (e.g. https://my-tunnel.trycloudflare.com/mcp or https://api.mycompany.com/mcp): ');
|
|
958
|
+
if (!upstreamUrl) {
|
|
959
|
+
console.error('❌ Upstream URL is required.');
|
|
960
|
+
process.exit(1);
|
|
961
|
+
}
|
|
962
|
+
let payoutWallet = creds?.payoutWallet;
|
|
963
|
+
if (!payoutWallet) {
|
|
964
|
+
payoutWallet = await promptQuestion('Base EVM payout wallet address (0x...): ');
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
const useSaved = await promptQuestion(`Use your account payout wallet ${payoutWallet}? (Y/n): `);
|
|
968
|
+
if (useSaved.toLowerCase() === 'n') {
|
|
969
|
+
payoutWallet = await promptQuestion('Base EVM payout wallet address (0x...): ');
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(payoutWallet)) {
|
|
973
|
+
console.error('❌ Invalid Base EVM wallet address.');
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
const tools = [];
|
|
977
|
+
let addingTools = true;
|
|
978
|
+
console.log('\nConfigure tools:');
|
|
979
|
+
while (addingTools) {
|
|
980
|
+
const tName = await promptQuestion('Tool name (e.g. get_intel): ');
|
|
981
|
+
if (!tName)
|
|
982
|
+
break;
|
|
983
|
+
const tType = (await promptQuestion('Pricing type [fixed/free] (default: fixed): ')).toLowerCase() || 'fixed';
|
|
984
|
+
let tPrice = '0';
|
|
985
|
+
if (tType === 'fixed') {
|
|
986
|
+
tPrice = (await promptQuestion('Price in USD per call (default: 0.01): ')) || '0.01';
|
|
987
|
+
}
|
|
988
|
+
tools.push({
|
|
989
|
+
toolName: tName,
|
|
990
|
+
type: tType === 'free' ? 'free' : 'fixed',
|
|
991
|
+
priceUsd: tPrice,
|
|
992
|
+
});
|
|
993
|
+
const more = await promptQuestion('Add another tool? (y/N): ');
|
|
994
|
+
if (more.toLowerCase() !== 'y') {
|
|
995
|
+
addingTools = false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (tools.length === 0) {
|
|
999
|
+
tools.push({ toolName: 'default_tool', type: 'free', priceUsd: '0' });
|
|
1000
|
+
}
|
|
1001
|
+
config = {
|
|
1002
|
+
serverId,
|
|
1003
|
+
serverName,
|
|
1004
|
+
payoutWallet,
|
|
1005
|
+
network: 'base',
|
|
1006
|
+
currency: 'USDC',
|
|
1007
|
+
tools,
|
|
1008
|
+
};
|
|
1009
|
+
const saveLocal = await promptQuestion('\nSave this configuration to ./mcpaid.config.json? (Y/n): ');
|
|
1010
|
+
if (saveLocal.toLowerCase() !== 'n') {
|
|
1011
|
+
const fileJson = {
|
|
1012
|
+
version: '1.0.0',
|
|
1013
|
+
server: {
|
|
1014
|
+
id: config.serverId,
|
|
1015
|
+
name: config.serverName,
|
|
1016
|
+
upstreamUrl,
|
|
1017
|
+
payoutWallet: config.payoutWallet,
|
|
1018
|
+
network: config.network,
|
|
1019
|
+
currency: config.currency,
|
|
1020
|
+
},
|
|
1021
|
+
tools: config.tools.map((t) => ({
|
|
1022
|
+
name: t.toolName,
|
|
1023
|
+
type: t.type,
|
|
1024
|
+
priceUsd: t.priceUsd,
|
|
1025
|
+
})),
|
|
1026
|
+
};
|
|
1027
|
+
writeFileSync(resolve(process.cwd(), 'mcpaid.config.json'), JSON.stringify(fileJson, null, 2), 'utf-8');
|
|
1028
|
+
console.log('✅ Created mcpaid.config.json in current directory');
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (hasFlag(args, 'tunnel') || hasFlag(args, 'dev')) {
|
|
1032
|
+
return handleDev(configPathArg);
|
|
1033
|
+
}
|
|
1034
|
+
const flagUpstream = getFlagValue(args, 'upstream') || getFlagValue(args, 'public-url') || getFlagValue(args, 'tunnel-url');
|
|
1035
|
+
if (flagUpstream) {
|
|
1036
|
+
upstreamUrl = flagUpstream;
|
|
1037
|
+
}
|
|
1038
|
+
if (isLocalOrPrivateHost(upstreamUrl)) {
|
|
1039
|
+
console.log(`
|
|
1040
|
+
⚠️ Local loopback address detected for upstream MCP: "${upstreamUrl}"
|
|
1041
|
+
The MCPaid Edge Gateway (${gatewayUrl}) runs globally on Cloudflare and cannot directly reach "127.0.0.1" on your computer.
|
|
1042
|
+
|
|
1043
|
+
Choose how to proceed:
|
|
1044
|
+
[1] Launch MCPaid Live Dev Tunnel (Recommended: zero-config, encrypted, with security shield)
|
|
1045
|
+
[2] Enter an existing public tunnel or domain URL
|
|
1046
|
+
[3] Cancel
|
|
1047
|
+
`);
|
|
1048
|
+
const opt = await promptQuestion('Select option (1/2/3, default: 1): ');
|
|
1049
|
+
if (opt === '2') {
|
|
1050
|
+
const tunnelInput = await promptQuestion('Enter your public tunnel URL (e.g. https://xxx.trycloudflare.com/mcp): ');
|
|
1051
|
+
if (!tunnelInput || !tunnelInput.trim()) {
|
|
1052
|
+
console.log('\nPublish cancelled.\n');
|
|
1053
|
+
process.exit(0);
|
|
1054
|
+
}
|
|
1055
|
+
let formatted = tunnelInput.trim();
|
|
1056
|
+
if (!formatted.startsWith('http://') && !formatted.startsWith('https://')) {
|
|
1057
|
+
formatted = 'https://' + formatted;
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
const testUrl = new URL(formatted);
|
|
1061
|
+
if (testUrl.pathname === '/' || !testUrl.pathname) {
|
|
1062
|
+
try {
|
|
1063
|
+
const origPath = new URL(upstreamUrl).pathname;
|
|
1064
|
+
if (origPath && origPath !== '/') {
|
|
1065
|
+
testUrl.pathname = origPath;
|
|
1066
|
+
formatted = testUrl.toString();
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
catch { }
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
catch {
|
|
1073
|
+
console.error('❌ Invalid tunnel URL format.');
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
if (isLocalOrPrivateHost(formatted)) {
|
|
1077
|
+
console.error('❌ Address is still a local/private host. A public URL is required for edge publishing.');
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
upstreamUrl = formatted;
|
|
1081
|
+
if (candidatePath && existsSync(resolve(process.cwd(), candidatePath))) {
|
|
1082
|
+
const updateLocal = await promptQuestion(`Update upstreamUrl in ${candidatePath} to "${upstreamUrl}"? (Y/n): `);
|
|
1083
|
+
if (updateLocal.toLowerCase() !== 'n') {
|
|
1084
|
+
try {
|
|
1085
|
+
const rawJson = JSON.parse(readFileSync(resolve(process.cwd(), candidatePath), 'utf-8'));
|
|
1086
|
+
if (rawJson.server) {
|
|
1087
|
+
rawJson.server.upstreamUrl = upstreamUrl;
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
rawJson.upstreamUrl = upstreamUrl;
|
|
1091
|
+
}
|
|
1092
|
+
writeFileSync(resolve(process.cwd(), candidatePath), JSON.stringify(rawJson, null, 2), 'utf-8');
|
|
1093
|
+
console.log(`✅ Updated ${candidatePath} with public tunnel URL.`);
|
|
1094
|
+
}
|
|
1095
|
+
catch { }
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
else if (opt === '3') {
|
|
1100
|
+
console.log('\nPublish cancelled.\n');
|
|
1101
|
+
process.exit(0);
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
// Default: Launch MCPaid Live Dev Tunnel
|
|
1105
|
+
return handleDev(configPathArg);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
console.log(`\n🚀 Publishing server "${config.serverId}" to ${gatewayUrl}...`);
|
|
1109
|
+
const res = await fetch(`${gatewayUrl}/v1/servers`, {
|
|
1110
|
+
method: 'POST',
|
|
1111
|
+
headers: {
|
|
1112
|
+
'Content-Type': 'application/json',
|
|
1113
|
+
Authorization: `Bearer ${effectiveToken}`,
|
|
1114
|
+
},
|
|
1115
|
+
body: JSON.stringify({ config, upstreamUrl }),
|
|
1116
|
+
});
|
|
1117
|
+
const data = (await res.json().catch(() => ({})));
|
|
1118
|
+
if (!res.ok || !data.success) {
|
|
1119
|
+
console.error(`❌ Publish failed: ${data.error || JSON.stringify(data)}`);
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
// Update saved credentials payout wallet if not already present
|
|
1123
|
+
if (creds && !creds.payoutWallet && config.payoutWallet) {
|
|
1124
|
+
creds.payoutWallet = config.payoutWallet;
|
|
1125
|
+
saveCredentials(creds);
|
|
1126
|
+
}
|
|
1127
|
+
console.log(`
|
|
1128
|
+
======================================================
|
|
1129
|
+
🎉 MCP Server Successfully Published to MCPaid!
|
|
1130
|
+
======================================================
|
|
1131
|
+
• Server ID: ${config.serverId}
|
|
1132
|
+
• Name: ${config.serverName}
|
|
1133
|
+
• Edge Gateway: ${gatewayUrl}/mcp/${config.serverId}
|
|
1134
|
+
• Upstream: ${upstreamUrl}
|
|
1135
|
+
• Payout: ${config.payoutWallet} (Base L2)
|
|
1136
|
+
• Configured Tools (${config.tools.length}):
|
|
1137
|
+
${config.tools.map((t) => ` • ${t.toolName.padEnd(20)} -> ${t.type === 'free' ? 'FREE' : `$${t.priceUsd} USDC`}`).join('\n')}
|
|
1138
|
+
|
|
1139
|
+
📋 Agent / Client Connection:
|
|
1140
|
+
npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${config.serverId}
|
|
1141
|
+
======================================================
|
|
1142
|
+
`);
|
|
1143
|
+
}
|
|
1144
|
+
async function handleServerList() {
|
|
1145
|
+
const args = process.argv.slice(3);
|
|
1146
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
1147
|
+
const creds = loadCredentials();
|
|
1148
|
+
const token = creds?.token || process.env.MCPAID_TOKEN;
|
|
1149
|
+
if (!token) {
|
|
1150
|
+
console.log('❌ You must be logged in to view your registered servers.');
|
|
1151
|
+
console.log('Run: npx @mcpaid/sdk login');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
console.log(`Fetching registered servers from ${gatewayUrl}...`);
|
|
1155
|
+
const res = await fetch(`${gatewayUrl}/v1/developer/dashboard-data`, {
|
|
1156
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1157
|
+
});
|
|
1158
|
+
const data = (await res.json().catch(() => ({})));
|
|
1159
|
+
if (!res.ok || !data.success) {
|
|
1160
|
+
console.error(`❌ Failed to fetch servers: ${data.error || JSON.stringify(data)}`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const servers = data.servers || [];
|
|
1164
|
+
if (servers.length === 0) {
|
|
1165
|
+
console.log(`\nNo MCP servers registered under this account yet.`);
|
|
1166
|
+
console.log(`Publish your first server using: npx @mcpaid/sdk publish\n`);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
console.log(`
|
|
1170
|
+
======================================================
|
|
1171
|
+
🌐 Registered MCPaid Servers (${servers.length})
|
|
1172
|
+
======================================================`);
|
|
1173
|
+
for (const s of servers) {
|
|
1174
|
+
console.log(`• ID: ${s.server_id}`);
|
|
1175
|
+
console.log(` Name: ${s.name || s.server_id}`);
|
|
1176
|
+
console.log(` Gateway: ${gatewayUrl}/mcp/${s.server_id}`);
|
|
1177
|
+
console.log(` Upstream: ${s.upstream_url}`);
|
|
1178
|
+
console.log(` Payout: ${s.payout_wallet}`);
|
|
1179
|
+
console.log(` Tools: ${(s.tools || []).map((t) => `${t.tool_name} (${t.pricing_type === 'free' ? 'FREE' : '$' + t.price_usd})`).join(', ') || 'None'}`);
|
|
1180
|
+
console.log(` Bridge: npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${s.server_id}`);
|
|
1181
|
+
console.log(`──────────────────────────────────────────────────────`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async function handleServerRemove(targetServerId) {
|
|
1185
|
+
const args = process.argv.slice(3);
|
|
1186
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
1187
|
+
const isForce = args.includes('--force') || args.includes('-f') || args.includes('-y') || args.includes('--yes');
|
|
1188
|
+
let serverId = targetServerId;
|
|
1189
|
+
if (serverId && serverId.startsWith('-')) {
|
|
1190
|
+
serverId = undefined;
|
|
1191
|
+
}
|
|
1192
|
+
if (!serverId) {
|
|
1193
|
+
const nonFlags = args.filter(a => !a.startsWith('-') && a !== 'remove' && a !== 'rm' && a !== 'delete' && a !== 'disconnect');
|
|
1194
|
+
if (nonFlags.length > 0) {
|
|
1195
|
+
serverId = nonFlags[0];
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const creds = loadCredentials();
|
|
1199
|
+
const token = creds?.token || process.env.MCPAID_TOKEN;
|
|
1200
|
+
if (!token) {
|
|
1201
|
+
console.log('❌ You must be logged in to disconnect or remove an MCP server.');
|
|
1202
|
+
console.log('Run: npx @mcpaid/sdk login');
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (!serverId) {
|
|
1206
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1207
|
+
serverId = await new Promise((res) => {
|
|
1208
|
+
rl.question('Enter the ID of the MCP server to disconnect and remove: ', (ans) => {
|
|
1209
|
+
rl.close();
|
|
1210
|
+
res(ans.trim());
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
if (!serverId) {
|
|
1215
|
+
console.error('❌ Error: Server ID is required. Example: npx @mcpaid/sdk server remove my-custom-mcp');
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!isForce) {
|
|
1219
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1220
|
+
const answer = await new Promise((res) => {
|
|
1221
|
+
rl.question(`⚠️ Are you sure you want to disconnect and permanently remove "${serverId}" from the MCPaid network? (y/N): `, (ans) => {
|
|
1222
|
+
rl.close();
|
|
1223
|
+
res(ans.trim().toLowerCase());
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
1227
|
+
console.log('Operation cancelled.');
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
console.log(`Disconnecting server "${serverId}" from ${gatewayUrl}...`);
|
|
1232
|
+
const res = await fetch(`${gatewayUrl}/v1/servers/${encodeURIComponent(serverId)}`, {
|
|
1233
|
+
method: 'DELETE',
|
|
1234
|
+
headers: {
|
|
1235
|
+
Authorization: `Bearer ${token}`,
|
|
1236
|
+
'Content-Type': 'application/json',
|
|
1237
|
+
},
|
|
1238
|
+
});
|
|
1239
|
+
const data = (await res.json().catch(() => ({})));
|
|
1240
|
+
if (!res.ok || !data.success) {
|
|
1241
|
+
console.error(`❌ Failed to disconnect server: ${data.error || JSON.stringify(data)}`);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
console.log(`
|
|
1245
|
+
======================================================
|
|
1246
|
+
🗑️ MCP Server Disconnected & Removed
|
|
1247
|
+
======================================================
|
|
1248
|
+
• Server ID: ${serverId}
|
|
1249
|
+
• Status: Removed from MCPaid network & account records
|
|
1250
|
+
• Gateway: ${gatewayUrl}/mcp/${serverId} (Deactivated)
|
|
1251
|
+
======================================================
|
|
1252
|
+
Clean slate achieved. You can republish anytime with:
|
|
1253
|
+
npx @mcpaid/sdk publish
|
|
1254
|
+
`);
|
|
1255
|
+
}
|
|
1256
|
+
async function handleServer() {
|
|
1257
|
+
const sub = process.argv[3];
|
|
1258
|
+
if (sub === 'list' || sub === 'ls') {
|
|
1259
|
+
return handleServerList();
|
|
1260
|
+
}
|
|
1261
|
+
if (sub === 'add' || sub === 'register' || sub === 'publish' || sub === 'deploy') {
|
|
1262
|
+
return handlePublish(process.argv[4]);
|
|
1263
|
+
}
|
|
1264
|
+
if (sub === 'remove' || sub === 'rm' || sub === 'delete' || sub === 'disconnect') {
|
|
1265
|
+
return handleServerRemove(process.argv[4]);
|
|
1266
|
+
}
|
|
1267
|
+
console.log(`
|
|
1268
|
+
Usage: npx @mcpaid/sdk server <command>
|
|
1269
|
+
|
|
1270
|
+
Commands:
|
|
1271
|
+
add [config-path] Publish and monetize an MCP server on MCPaid Edge Gateway
|
|
1272
|
+
list List all MCP servers registered under your account
|
|
1273
|
+
remove <server-id> Disconnect and remove an MCP server from MCPaid network
|
|
1274
|
+
`);
|
|
1275
|
+
}
|
|
1276
|
+
async function handleWithdraw(amountArg) {
|
|
1277
|
+
const flags = process.argv.slice(2);
|
|
1278
|
+
const gatewayUrl = (getFlagValue(flags, '--gateway') || process.env.MCPAID_GATEWAY || 'https://mcpaid.dev').replace(/\/+$/, '');
|
|
1279
|
+
const isYes = flags.includes('-y') || flags.includes('--yes');
|
|
1280
|
+
const creds = loadCredentials();
|
|
1281
|
+
if (!creds || !creds.token) {
|
|
1282
|
+
console.log('\n🔒 Authentication required to withdraw developer earnings.');
|
|
1283
|
+
const shouldLogin = (await promptQuestion('Would you like to sign in now? (Y/n): ')).toLowerCase();
|
|
1284
|
+
if (shouldLogin === 'n' || shouldLogin === 'no') {
|
|
1285
|
+
console.log('Aborted. Sign in anytime with: npx @mcpaid/sdk login');
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
await handleLogin();
|
|
1289
|
+
}
|
|
1290
|
+
const updatedCreds = loadCredentials();
|
|
1291
|
+
if (!updatedCreds || !updatedCreds.token) {
|
|
1292
|
+
console.error('❌ You must be authenticated to request withdrawals.');
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
console.log(`\nConnecting to MCPaid Edge Gateway (${gatewayUrl})...`);
|
|
1296
|
+
const sessionRes = await fetch(`${gatewayUrl}/v1/developer/dashboard-data`, {
|
|
1297
|
+
headers: { Authorization: `Bearer ${updatedCreds.token}` },
|
|
1298
|
+
});
|
|
1299
|
+
const sessionData = (await sessionRes.json().catch(() => ({})));
|
|
1300
|
+
if (!sessionRes.ok || !sessionData.success) {
|
|
1301
|
+
console.error(`❌ Failed to retrieve balance: ${sessionData.error || 'Unauthorized'}`);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const user = sessionData.user;
|
|
1305
|
+
const balance = sessionData.balance || { availableBalanceMicro: 0 };
|
|
1306
|
+
const availMicro = Number(balance.availableBalanceMicro || 0);
|
|
1307
|
+
const availUsdc = (availMicro / 1_000_000).toFixed(4);
|
|
1308
|
+
if (!user.payoutWallet) {
|
|
1309
|
+
console.error('❌ No Base L2 payout wallet configured on your account.');
|
|
1310
|
+
console.log('Please configure your payout wallet in the dashboard (https://mcpaid.dev/dashboard).');
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (availMicro <= 0) {
|
|
1314
|
+
console.log(`\nℹ️ No available balance to withdraw.`);
|
|
1315
|
+
console.log(`• Available Balance: $${availUsdc} USDC`);
|
|
1316
|
+
console.log(`• Total Gross Volume: $${((balance.totalGrossMicro || 0) / 1_000_000).toFixed(4)} USDC`);
|
|
1317
|
+
console.log(`• Payout Wallet: ${user.payoutWallet}`);
|
|
1318
|
+
console.log(`\nInvocations from remote AI agents will credit your balance automatically.\n`);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
let toWithdrawMicro = availMicro;
|
|
1322
|
+
if (amountArg && !amountArg.startsWith('-')) {
|
|
1323
|
+
const parsedUsd = parseFloat(amountArg);
|
|
1324
|
+
if (isNaN(parsedUsd) || parsedUsd <= 0) {
|
|
1325
|
+
console.error(`❌ Invalid withdrawal amount: "${amountArg}". Example: npx @mcpaid/sdk withdraw 10.00`);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
toWithdrawMicro = Math.round(parsedUsd * 1_000_000);
|
|
1329
|
+
if (toWithdrawMicro > availMicro) {
|
|
1330
|
+
console.error(`❌ Requested withdrawal ($${parsedUsd.toFixed(4)}) exceeds available balance ($${availUsdc}).`);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const withdrawAmountUsdc = (toWithdrawMicro / 1_000_000).toFixed(4);
|
|
1335
|
+
console.log(`
|
|
1336
|
+
======================================================
|
|
1337
|
+
💸 MCPaid Payout Withdrawal
|
|
1338
|
+
======================================================
|
|
1339
|
+
• Available Balance: $${availUsdc} USDC
|
|
1340
|
+
• Amount to Withdraw: $${withdrawAmountUsdc} USDC
|
|
1341
|
+
• Network: Base Mainnet (Chain ID 8453)
|
|
1342
|
+
• Destination Wallet: ${user.payoutWallet}
|
|
1343
|
+
• Protocol Fee: 0.00% (Free)
|
|
1344
|
+
======================================================
|
|
1345
|
+
`);
|
|
1346
|
+
if (!isYes) {
|
|
1347
|
+
const confirmAns = (await promptQuestion(`Proceed with withdrawal of $${withdrawAmountUsdc} USDC to Base L2? (Y/n): `)).toLowerCase();
|
|
1348
|
+
if (confirmAns === 'n' || confirmAns === 'no') {
|
|
1349
|
+
console.log('Withdrawal cancelled.');
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
console.log(`Submitting withdrawal request to Base L2 settlement pipeline...`);
|
|
1354
|
+
const res = await fetch(`${gatewayUrl}/v1/developer/withdraw`, {
|
|
1355
|
+
method: 'POST',
|
|
1356
|
+
headers: {
|
|
1357
|
+
Authorization: `Bearer ${updatedCreds.token}`,
|
|
1358
|
+
'Content-Type': 'application/json',
|
|
1359
|
+
},
|
|
1360
|
+
body: JSON.stringify({ amountMicro: toWithdrawMicro }),
|
|
1361
|
+
});
|
|
1362
|
+
const data = (await res.json().catch(() => ({})));
|
|
1363
|
+
if (!res.ok || !data.success) {
|
|
1364
|
+
console.error(`❌ Withdrawal failed: ${data.error || JSON.stringify(data)}`);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
const w = data.withdrawal;
|
|
1368
|
+
console.log(`
|
|
1369
|
+
======================================================
|
|
1370
|
+
✅ Withdrawal Successfully Dispatched!
|
|
1371
|
+
======================================================
|
|
1372
|
+
• Withdrawal ID: ${w.id}
|
|
1373
|
+
• Amount: $${(w.amountMicro / 1_000_000).toFixed(4)} USDC
|
|
1374
|
+
• Status: ${w.status.toUpperCase()} (Queued for Settlement Batch)
|
|
1375
|
+
• Destination: ${w.developerWallet}
|
|
1376
|
+
• BaseScan: https://basescan.org/address/${w.developerWallet}
|
|
1377
|
+
======================================================
|
|
1378
|
+
Your available balance has been updated. View past withdrawals with:
|
|
1379
|
+
npx @mcpaid/sdk withdrawals
|
|
1380
|
+
`);
|
|
1381
|
+
}
|
|
1382
|
+
async function handleWithdrawals() {
|
|
1383
|
+
const flags = process.argv.slice(2);
|
|
1384
|
+
const gatewayUrl = (getFlagValue(flags, '--gateway') || process.env.MCPAID_GATEWAY || 'https://mcpaid.dev').replace(/\/+$/, '');
|
|
1385
|
+
const creds = loadCredentials();
|
|
1386
|
+
if (!creds || !creds.token) {
|
|
1387
|
+
console.error('❌ You must be authenticated to view withdrawals. Run: npx @mcpaid/sdk login');
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const res = await fetch(`${gatewayUrl}/v1/developer/withdrawals`, {
|
|
1391
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
1392
|
+
});
|
|
1393
|
+
const data = (await res.json().catch(() => ({})));
|
|
1394
|
+
if (!res.ok || !data.success) {
|
|
1395
|
+
console.error(`❌ Failed to retrieve withdrawals: ${data.error || 'Error'}`);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const list = data.withdrawals || [];
|
|
1399
|
+
console.log(`
|
|
1400
|
+
======================================================
|
|
1401
|
+
📋 Base L2 Payout & Withdrawal History (${list.length})
|
|
1402
|
+
======================================================`);
|
|
1403
|
+
if (list.length === 0) {
|
|
1404
|
+
console.log('No withdrawal requests recorded yet. Withdraw anytime with:');
|
|
1405
|
+
console.log('npx @mcpaid/sdk withdraw');
|
|
1406
|
+
console.log('======================================================\n');
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
for (const w of list) {
|
|
1410
|
+
const date = new Date(w.createdAt || w.created_at).toLocaleString();
|
|
1411
|
+
const amount = `$${(w.amountMicro / 1_000_000).toFixed(4)} USDC`;
|
|
1412
|
+
const status = (w.status || 'processing').toUpperCase();
|
|
1413
|
+
console.log(`• [${status}] ${amount} -> ${w.developerWallet}`);
|
|
1414
|
+
console.log(` ID: ${w.id} | Date: ${date}`);
|
|
1415
|
+
if (w.txHash) {
|
|
1416
|
+
console.log(` TxHash: https://basescan.org/tx/${w.txHash}`);
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
console.log(` Address: https://basescan.org/address/${w.developerWallet}`);
|
|
1420
|
+
}
|
|
1421
|
+
console.log('');
|
|
1422
|
+
}
|
|
1423
|
+
console.log('======================================================\n');
|
|
1424
|
+
}
|
|
1425
|
+
async function handleRelayer() {
|
|
1426
|
+
const flags = process.argv.slice(2);
|
|
1427
|
+
const isRunLocal = flags.includes('--run') || flags.includes('-r');
|
|
1428
|
+
if (isRunLocal && process.env.MCPAID_OPERATOR !== '1') {
|
|
1429
|
+
console.log(`
|
|
1430
|
+
======================================================
|
|
1431
|
+
⛔ Operator-only command
|
|
1432
|
+
======================================================
|
|
1433
|
+
Manual settlement runs disburse real on-chain funds and are
|
|
1434
|
+
reserved for gateway operators under OPERATOR-LICENSE.md.
|
|
1435
|
+
|
|
1436
|
+
The hosted mcpaid.dev relayer settles automatically every
|
|
1437
|
+
60 seconds — developers do not need this command. If you
|
|
1438
|
+
operate your own gateway under an operator agreement, re-run
|
|
1439
|
+
with MCPAID_OPERATOR=1.
|
|
1440
|
+
======================================================
|
|
1441
|
+
`);
|
|
1442
|
+
process.exit(1);
|
|
1443
|
+
}
|
|
1444
|
+
if (!isRunLocal) {
|
|
1445
|
+
console.log(`
|
|
1446
|
+
======================================================
|
|
1447
|
+
⏱️ MCPaid Automated Base L2 Settlement Relayer
|
|
1448
|
+
======================================================
|
|
1449
|
+
• Schedule: Runs automatically EVERY 1 MINUTE (* * * * *)
|
|
1450
|
+
• Execution: Isolated Cloudflare Edge Cron Trigger
|
|
1451
|
+
• Security: HTTP triggers are disabled. Only internal cron
|
|
1452
|
+
triggers or verified local operator scripts can
|
|
1453
|
+
disburse on-chain funds.
|
|
1454
|
+
• Operator Mode: To run a manual settlement cycle directly using
|
|
1455
|
+
your local relayer credentials:
|
|
1456
|
+
npx @mcpaid/sdk relayer --run
|
|
1457
|
+
======================================================
|
|
1458
|
+
`);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
console.log(`\nExecuting local Base L2 settlement relayer...`);
|
|
1462
|
+
const scriptPath = resolve(fileURLToPath(import.meta.url), '../../scripts/settle-withdrawals.ts');
|
|
1463
|
+
if (existsSync(scriptPath)) {
|
|
1464
|
+
const child = spawn('npx', ['tsx', scriptPath], { stdio: 'inherit' });
|
|
1465
|
+
child.on('exit', (code) => {
|
|
1466
|
+
if (code !== 0)
|
|
1467
|
+
process.exit(code || 1);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
console.log(`Error: Settlement script not found at ${scriptPath}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
export async function runCli() {
|
|
1475
|
+
const command = process.argv[2];
|
|
1476
|
+
switch (command) {
|
|
1477
|
+
case 'init':
|
|
1478
|
+
await handleInit();
|
|
1479
|
+
break;
|
|
1480
|
+
case 'validate':
|
|
1481
|
+
await handleValidate(process.argv[3]);
|
|
1482
|
+
break;
|
|
1483
|
+
case 'start':
|
|
1484
|
+
await handleStart(process.argv[3]);
|
|
1485
|
+
break;
|
|
1486
|
+
case 'dev':
|
|
1487
|
+
await handleDev(process.argv[3]);
|
|
1488
|
+
break;
|
|
1489
|
+
case 'agent-proxy':
|
|
1490
|
+
case 'bridge':
|
|
1491
|
+
await handleBridge();
|
|
1492
|
+
break;
|
|
1493
|
+
case 'wallet':
|
|
1494
|
+
await handleWallet(process.argv[3]);
|
|
1495
|
+
break;
|
|
1496
|
+
case 'login':
|
|
1497
|
+
case 'auth':
|
|
1498
|
+
case 'signin':
|
|
1499
|
+
await handleLogin();
|
|
1500
|
+
break;
|
|
1501
|
+
case 'logout':
|
|
1502
|
+
case 'signout':
|
|
1503
|
+
await handleLogout();
|
|
1504
|
+
break;
|
|
1505
|
+
case 'whoami':
|
|
1506
|
+
await handleWhoami();
|
|
1507
|
+
break;
|
|
1508
|
+
case 'publish':
|
|
1509
|
+
case 'deploy':
|
|
1510
|
+
await handlePublish(process.argv[3]);
|
|
1511
|
+
break;
|
|
1512
|
+
case 'tunnel':
|
|
1513
|
+
await handleTunnel(process.argv[3]);
|
|
1514
|
+
break;
|
|
1515
|
+
case 'remove':
|
|
1516
|
+
case 'disconnect':
|
|
1517
|
+
case 'rm':
|
|
1518
|
+
await handleServerRemove(process.argv[3]);
|
|
1519
|
+
break;
|
|
1520
|
+
case 'server':
|
|
1521
|
+
case 'servers':
|
|
1522
|
+
await handleServer();
|
|
1523
|
+
break;
|
|
1524
|
+
case 'withdraw':
|
|
1525
|
+
case 'payout':
|
|
1526
|
+
await handleWithdraw(process.argv[3]);
|
|
1527
|
+
break;
|
|
1528
|
+
case 'withdrawals':
|
|
1529
|
+
case 'payouts':
|
|
1530
|
+
await handleWithdrawals();
|
|
1531
|
+
break;
|
|
1532
|
+
case 'relayer':
|
|
1533
|
+
case 'settle':
|
|
1534
|
+
await handleRelayer();
|
|
1535
|
+
break;
|
|
1536
|
+
default:
|
|
1537
|
+
printHelp();
|
|
1538
|
+
break;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (process.argv[1]) {
|
|
1542
|
+
try {
|
|
1543
|
+
const realArgv1 = existsSync(process.argv[1]) ? realpathSync(process.argv[1]) : resolve(process.argv[1]);
|
|
1544
|
+
if (realArgv1 === fileURLToPath(import.meta.url)) {
|
|
1545
|
+
runCli().catch((err) => {
|
|
1546
|
+
console.error(`Fatal error: ${err.message}`);
|
|
1547
|
+
process.exit(1);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
catch {
|
|
1552
|
+
if (resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
1553
|
+
runCli().catch((err) => {
|
|
1554
|
+
console.error(`Fatal error: ${err.message}`);
|
|
1555
|
+
process.exit(1);
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
//# sourceMappingURL=cli.js.map
|