@mcpaid/sdk 2.0.0 โ 2.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 +179 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +139 -3
- package/dist/cli.js.map +1 -1
- package/dist/edge-receipts.d.ts +6 -0
- package/dist/edge-receipts.d.ts.map +1 -0
- package/dist/edge-receipts.js +6 -0
- package/dist/edge-receipts.js.map +1 -0
- package/dist/gateway/toolpay-gateway.d.ts +22 -1
- package/dist/gateway/toolpay-gateway.d.ts.map +1 -1
- package/dist/gateway/toolpay-gateway.js +48 -4
- package/dist/gateway/toolpay-gateway.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/settlement/edge-receipts.d.ts +158 -0
- package/dist/settlement/edge-receipts.d.ts.map +1 -0
- package/dist/settlement/edge-receipts.js +407 -0
- package/dist/settlement/edge-receipts.js.map +1 -0
- package/dist/utils/units.js +1 -1
- package/dist/utils/units.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://basescan.org)
|
|
6
6
|
[](https://basescan.org/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
|
|
7
|
-
[](test)
|
|
8
8
|
|
|
9
9
|
**MCPaid** enables developers of [Model Context Protocol (MCP)](https://modelcontextprotocol.io) servers to seamlessly monetize tools with pay-per-use micropayments (e.g. $0.005 / call), gasless AI agent authorizations via **HTTP 402 + EIP-712**, and **automated 60-second on-chain USDC payouts on Base L2**.
|
|
10
10
|
|
|
@@ -27,16 +27,22 @@
|
|
|
27
27
|
- When a developer requests a withdrawal, Cloudflare Workers' isolated cron relayer automatically executes the on-chain ERC-20 transfer on Base L2 every **60 seconds**, stamping confirmed BaseScan receipts directly into the dashboard.
|
|
28
28
|
- **Zero HTTP Attack Surface**: All payout execution paths run strictly via internal edge cron triggers or authenticated local operator scripts (`mcpaid relayer --run`).
|
|
29
29
|
|
|
30
|
-
4. **
|
|
30
|
+
4. **Cryptographic Edge Receipts (`X-MCPaid-Receipt`) & Downstream Enforcement**:
|
|
31
|
+
- Single-use, tamper-proof HMAC-SHA256 payment receipts injected by the edge gateway on proxied requests.
|
|
32
|
+
- Downstream origin backends, microservices, and databases verify payment proofs in **5 lines of code**, preventing forked clients or scrapers from bypassing the edge gateway.
|
|
33
|
+
- Per-server secret derivation via HKDF-SHA256 (`mcpaid_sec_...`) with zero-downtime 1-hour grace window key rotation.
|
|
34
|
+
- Built-in anti-replay nonce claim store (`MemoryReceiptStore`, `D1ReceiptStore`) and drop-in Express middleware (`createReceiptMiddleware`).
|
|
35
|
+
|
|
36
|
+
5. **Zero-Config Live Development Tunnel (`mcpaid dev`)**:
|
|
31
37
|
- Monetize MCP servers running locally on hardware (`http://127.0.0.1:3000/mcp`) without buying domains or renting cloud VPS servers.
|
|
32
38
|
- Launches an encrypted TLS 1.3 tunnel with an integrated **Security Shield** that drops direct bypass traffic with `403 Forbidden`.
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
6. **Server Management & Anti-Hijacking Security**:
|
|
35
41
|
- Clean-slate server de-registration via Web Dashboard or CLI (`mcpaid server remove <id>`).
|
|
36
42
|
- Ownership verification ensures server IDs cannot be overwritten by unauthorized third parties.
|
|
37
43
|
- Passwordless 2FA email authentication with 30-day sliding-window sessions.
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
7. **Agent Safety Circuit Breakers**:
|
|
40
46
|
- Built-in velocity limiters (sliding 1-minute window).
|
|
41
47
|
- Spend ceiling hard caps (`maxPricePerCallUsd`, `maxTotalBudgetUsd`).
|
|
42
48
|
- Infinite duplicate parameter loop detection.
|
|
@@ -316,6 +322,170 @@ print("Tool Result:", resp.json()["result"])
|
|
|
316
322
|
|
|
317
323
|
---
|
|
318
324
|
|
|
325
|
+
## ๐งพ Edge Receipts: Downstream Payment Enforcement
|
|
326
|
+
|
|
327
|
+
When an agent invokes a paid MCP tool, MCPaid's edge verifies the 402 micropayment, credits your ledger, and proxies the request to your upstream server. But what if your upstream server forwards the call to a downstream backend, database, microservice, or webhook (e.g. committing a database write, enqueueing an expensive job, or calling third-party APIs)?
|
|
328
|
+
|
|
329
|
+
Without cryptographic proof, your downstream backend cannot distinguish an authorized edge-settled call from an attacker or forked CLI hitting your internal endpoints directly.
|
|
330
|
+
|
|
331
|
+
**Edge Receipts** solve this downstream trust boundary:
|
|
332
|
+
1. When a paid call settles, the edge gateway mints a cryptographically signed receipt and injects it as an `X-MCPaid-Receipt` HTTP header (base64url-encoded JSON).
|
|
333
|
+
2. Your downstream backend verifies the receipt in **5 lines of code** with anti-replay defense and tool binding.
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
[ AI Agent ]
|
|
337
|
+
โ
|
|
338
|
+
โ 1. POST /mcp/:serverId (tools/call)
|
|
339
|
+
โผ
|
|
340
|
+
[ MCPaid Edge Gateway ]
|
|
341
|
+
โ
|
|
342
|
+
โ 2. HTTP 402 Payment Required (Price + Nonce)
|
|
343
|
+
โผ
|
|
344
|
+
[ AI Agent Pays ]
|
|
345
|
+
โ
|
|
346
|
+
โ 3. Signs gasless EIP-712 micro-permit & replays
|
|
347
|
+
โผ
|
|
348
|
+
[ MCPaid Edge Gateway ]
|
|
349
|
+
โ
|
|
350
|
+
โ 4. Settles micropayment on Base L2 ledger (97% Dev / 3% Platform)
|
|
351
|
+
โ 5. Mints signed receipt & proxies call with X-MCPaid-Receipt header
|
|
352
|
+
โผ
|
|
353
|
+
[ Downstream Server / Origin Backend ]
|
|
354
|
+
โ
|
|
355
|
+
โ 6. Verifies receipt in 5 lines (HMAC-SHA256 + atomic nonce claim)
|
|
356
|
+
โผ
|
|
357
|
+
[ Commits DB write / executes heavy compute / returns response ]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 1. Retrieve Your Server's Receipt Secret
|
|
361
|
+
|
|
362
|
+
Each server has a deterministic receipt secret derived via `HKDF-SHA256` from the edge master secret. Retrieve it via the CLI:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Display your server's receipt secret
|
|
366
|
+
npx @mcpaid/sdk server receipt-secret <serverId>
|
|
367
|
+
|
|
368
|
+
# Rotate secret (previous secret remains valid for 1-hour grace window)
|
|
369
|
+
npx @mcpaid/sdk server receipt-secret <serverId> --rotate
|
|
370
|
+
|
|
371
|
+
# Automatically append or update MCPAID_RECEIPT_SECRET in your local .env
|
|
372
|
+
npx @mcpaid/sdk server receipt-secret <serverId> --env
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 2. Downstream Verification Code Snippets
|
|
376
|
+
|
|
377
|
+
#### Option A: Express / Node.js Middleware
|
|
378
|
+
|
|
379
|
+
Use the built-in `createReceiptMiddleware` for drop-in Express protection:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import express from 'express';
|
|
383
|
+
import { createReceiptMiddleware, MemoryReceiptStore } from '@mcpaid/sdk';
|
|
384
|
+
|
|
385
|
+
const app = express();
|
|
386
|
+
app.use(express.json());
|
|
387
|
+
|
|
388
|
+
// Reject direct scrapers or un-settled calls in 5 lines
|
|
389
|
+
app.post(
|
|
390
|
+
'/v1/heavy-compute',
|
|
391
|
+
createReceiptMiddleware({
|
|
392
|
+
secret: process.env.MCPAID_RECEIPT_SECRET!,
|
|
393
|
+
expectedTool: 'heavy_compute',
|
|
394
|
+
store: new MemoryReceiptStore(), // Prevents replay attacks
|
|
395
|
+
}),
|
|
396
|
+
(req: any, res) => {
|
|
397
|
+
// req.mcpaidReceipt contains verified settlement metadata
|
|
398
|
+
res.json({ success: true, payer: req.mcpaidReceipt.agentWallet });
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
app.listen(3000);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### Option B: Cloudflare Worker / Serverless Edge
|
|
406
|
+
|
|
407
|
+
Validate inside a Cloudflare Worker or Edge Function using `verifyEdgeReceipt` and SQLite/D1 replay storage:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { verifyEdgeReceipt, D1ReceiptStore } from '@mcpaid/sdk';
|
|
411
|
+
|
|
412
|
+
export default {
|
|
413
|
+
async fetch(request: Request, env: any): Promise<Response> {
|
|
414
|
+
const error = await verifyEdgeReceipt(
|
|
415
|
+
{
|
|
416
|
+
secret: env.MCPAID_RECEIPT_SECRET,
|
|
417
|
+
previousSecret: env.MCPAID_PREVIOUS_RECEIPT_SECRET, // 1-hr rotation grace window
|
|
418
|
+
store: new D1ReceiptStore(env.DB), // Atomic SQLite anti-replay
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
receipt: request.headers.get('X-MCPaid-Receipt'),
|
|
422
|
+
expectedTool: 'cloud_sync_push',
|
|
423
|
+
maxAgeSeconds: 300, // 5-minute expiry
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (error) {
|
|
428
|
+
return new Response(JSON.stringify({ error: 'payment_required', message: error }), {
|
|
429
|
+
status: 402,
|
|
430
|
+
headers: { 'Content-Type': 'application/json' },
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Proceed with state-changing database write or API call
|
|
435
|
+
return new Response(JSON.stringify({ status: 'committed' }), { status: 200 });
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Option C: Python Downstream Backend (FastAPI / Flask)
|
|
441
|
+
|
|
442
|
+
Verify receipts in Python using standard library cryptographic primitives (`hmac`, `hashlib`):
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
import base64, hashlib, hmac, json, os, time
|
|
446
|
+
from fastapi import FastAPI, Header, HTTPException
|
|
447
|
+
|
|
448
|
+
app = FastAPI()
|
|
449
|
+
SECRET = os.environ["MCPAID_RECEIPT_SECRET"]
|
|
450
|
+
claimed_nonces = set()
|
|
451
|
+
|
|
452
|
+
def verify_receipt(receipt_header: str, expected_tool: str) -> dict:
|
|
453
|
+
if not receipt_header:
|
|
454
|
+
raise HTTPException(status_code=402, detail="Missing X-MCPaid-Receipt header")
|
|
455
|
+
|
|
456
|
+
# Pad base64url string if necessary and decode
|
|
457
|
+
padded = receipt_header + "=" * ((4 - len(receipt_header) % 4) % 4)
|
|
458
|
+
receipt = json.loads(base64.urlsafe_b64decode(padded).decode("utf-8"))
|
|
459
|
+
|
|
460
|
+
if receipt.get("v") != 1 or receipt.get("toolName") != expected_tool:
|
|
461
|
+
raise HTTPException(status_code=402, detail="Invalid receipt tool or version")
|
|
462
|
+
|
|
463
|
+
if time.time() > receipt.get("exp", 0) + 60:
|
|
464
|
+
raise HTTPException(status_code=402, detail="Receipt expired")
|
|
465
|
+
|
|
466
|
+
# Recompute HMAC-SHA256 on canonical JSON without 'sig'
|
|
467
|
+
sig = receipt.pop("sig", "")
|
|
468
|
+
canonical = json.dumps(receipt, sort_keys=True, separators=(",", ":"))
|
|
469
|
+
computed = hmac.new(SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
|
|
470
|
+
|
|
471
|
+
if not hmac.compare_digest(sig, computed):
|
|
472
|
+
raise HTTPException(status_code=402, detail="Cryptographic signature mismatch")
|
|
473
|
+
|
|
474
|
+
nonce = receipt["challengeNonce"]
|
|
475
|
+
if nonce in claimed_nonces:
|
|
476
|
+
raise HTTPException(status_code=402, detail="Receipt replay attack detected")
|
|
477
|
+
claimed_nonces.add(nonce)
|
|
478
|
+
|
|
479
|
+
return receipt
|
|
480
|
+
|
|
481
|
+
@app.post("/v1/db-commit")
|
|
482
|
+
def commit_action(x_mcpaid_receipt: str = Header(None)):
|
|
483
|
+
verified = verify_receipt(x_mcpaid_receipt, expected_tool="db_commit")
|
|
484
|
+
return {"status": "success", "amount_paid_micro": verified["amountMicro"]}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
319
489
|
## ๐ ๏ธ CLI Command Reference
|
|
320
490
|
|
|
321
491
|
| Command | Arguments / Flags | Description |
|
|
@@ -327,6 +497,7 @@ print("Tool Result:", resp.json()["result"])
|
|
|
327
497
|
| `mcpaid publish` | `[config-path] [--gateway <url>]` | Publish and monetize an MCP server on MCPaid Edge Gateway. |
|
|
328
498
|
| `mcpaid server list` | `โ` | List all MCP servers and pricing rules registered under your account. |
|
|
329
499
|
| `mcpaid server remove` | `<server-id>` | Disconnect and permanently remove an MCP server (clean slate). |
|
|
500
|
+
| `mcpaid server receipt-secret` | `<id> [--rotate] [--env]` | Retrieve or rotate HMAC-SHA256 Edge Receipt secret for backend verification. |
|
|
330
501
|
| `mcpaid login` | `[--email <addr>]` | Sign in with passwordless 2FA email confirmation code. |
|
|
331
502
|
| `mcpaid whoami` | `โ` | Display authenticated developer profile and payout wallet. |
|
|
332
503
|
| `mcpaid logout` | `โ` | Revoke active session token and wipe local credentials. |
|
|
@@ -352,16 +523,16 @@ print("Tool Result:", resp.json()["result"])
|
|
|
352
523
|
|
|
353
524
|
## ๐งช Comprehensive Test Suite
|
|
354
525
|
|
|
355
|
-
MCPaid includes **
|
|
526
|
+
MCPaid includes **163 automated unit and integration tests** across 25 suites covering edge handlers, cryptographic verifiers, smart contract splits, financial ledgers, edge receipts, and the automated relayer:
|
|
356
527
|
|
|
357
528
|
```bash
|
|
358
529
|
npm test
|
|
359
530
|
```
|
|
360
531
|
|
|
361
532
|
```
|
|
362
|
-
# tests
|
|
363
|
-
# suites
|
|
364
|
-
# pass
|
|
533
|
+
# tests 163
|
|
534
|
+
# suites 25
|
|
535
|
+
# pass 163
|
|
365
536
|
# fail 0
|
|
366
537
|
```
|
|
367
538
|
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;GAGG;AAqDH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,wBAAgB,eAAe,IAAI,cAAc,GAAG,IAAI,CASvD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAG3D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAOvC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW7D;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;GAGG;AAqDH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,wBAAgB,eAAe,IAAI,cAAc,GAAG,IAAI,CASvD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAG3D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAOvC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW7D;AA+gBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAmB5D;AAkkCD,wBAAsB,MAAM,kBAkE3B"}
|
package/dist/cli.js
CHANGED
|
@@ -126,6 +126,7 @@ Commands:
|
|
|
126
126
|
server add [config-path] Register an MCP server on MCPaid Edge Gateway
|
|
127
127
|
server list List your registered MCP servers
|
|
128
128
|
server remove <id> Disconnect and remove an MCP server from MCPaid
|
|
129
|
+
server receipt-secret <id> View or rotate Edge Receipt secret for backend verification (--rotate, --env)
|
|
129
130
|
login Sign in to your MCPaid developer account
|
|
130
131
|
logout Log out and remove local credentials
|
|
131
132
|
whoami Show currently authenticated developer account
|
|
@@ -1124,6 +1125,7 @@ Choose how to proceed:
|
|
|
1124
1125
|
creds.payoutWallet = config.payoutWallet;
|
|
1125
1126
|
saveCredentials(creds);
|
|
1126
1127
|
}
|
|
1128
|
+
const receiptSecret = data.receiptSecret;
|
|
1127
1129
|
console.log(`
|
|
1128
1130
|
======================================================
|
|
1129
1131
|
๐ MCP Server Successfully Published to MCPaid!
|
|
@@ -1133,6 +1135,7 @@ Choose how to proceed:
|
|
|
1133
1135
|
โข Edge Gateway: ${gatewayUrl}/mcp/${config.serverId}
|
|
1134
1136
|
โข Upstream: ${upstreamUrl}
|
|
1135
1137
|
โข Payout: ${config.payoutWallet} (Base L2)
|
|
1138
|
+
${receiptSecret ? `โข Edge Receipt Secret: ${receiptSecret}` : ''}
|
|
1136
1139
|
โข Configured Tools (${config.tools.length}):
|
|
1137
1140
|
${config.tools.map((t) => ` โข ${t.toolName.padEnd(20)} -> ${t.type === 'free' ? 'FREE' : `$${t.priceUsd} USDC`}`).join('\n')}
|
|
1138
1141
|
|
|
@@ -1140,6 +1143,31 @@ ${config.tools.map((t) => ` โข ${t.toolName.padEnd(20)} -> ${t.type === 'free
|
|
|
1140
1143
|
npx @mcpaid/sdk bridge --gateway ${gatewayUrl}/mcp/${config.serverId}
|
|
1141
1144
|
======================================================
|
|
1142
1145
|
`);
|
|
1146
|
+
if (receiptSecret) {
|
|
1147
|
+
console.log(`๐ Downstream Verification:
|
|
1148
|
+
Use verifyEdgeReceipt() from @mcpaid/sdk in your backend to ensure callers actually paid at the edge.`);
|
|
1149
|
+
const envPath = resolve(process.cwd(), '.env');
|
|
1150
|
+
const hasEnvFlag = hasFlag(args, 'env');
|
|
1151
|
+
const shouldSave = hasEnvFlag
|
|
1152
|
+
? true
|
|
1153
|
+
: (await promptQuestion('Save MCPAID_RECEIPT_SECRET to .env? (Y/n): ')).toLowerCase() !== 'n';
|
|
1154
|
+
if (shouldSave) {
|
|
1155
|
+
try {
|
|
1156
|
+
let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
|
|
1157
|
+
if (envContent.includes('MCPAID_RECEIPT_SECRET=')) {
|
|
1158
|
+
envContent = envContent.replace(/MCPAID_RECEIPT_SECRET=.*/g, `MCPAID_RECEIPT_SECRET=${receiptSecret}`);
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
envContent += `\n# MCPaid Edge Receipt Secret for backend verification\nMCPAID_RECEIPT_SECRET=${receiptSecret}\n`;
|
|
1162
|
+
}
|
|
1163
|
+
writeFileSync(envPath, envContent, 'utf-8');
|
|
1164
|
+
console.log(`โ
Saved MCPAID_RECEIPT_SECRET to ${envPath}\n`);
|
|
1165
|
+
}
|
|
1166
|
+
catch (err) {
|
|
1167
|
+
console.warn(`Could not update .env: ${err.message}`);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1143
1171
|
}
|
|
1144
1172
|
async function handleServerList() {
|
|
1145
1173
|
const args = process.argv.slice(3);
|
|
@@ -1253,6 +1281,110 @@ Clean slate achieved. You can republish anytime with:
|
|
|
1253
1281
|
npx @mcpaid/sdk publish
|
|
1254
1282
|
`);
|
|
1255
1283
|
}
|
|
1284
|
+
async function handleServerReceiptSecret(targetServerId) {
|
|
1285
|
+
const args = process.argv.slice(3);
|
|
1286
|
+
const gatewayUrl = getFlagValue(args, 'gateway') || process.env.MCPAID_GATEWAY_URL || 'https://mcpaid.dev';
|
|
1287
|
+
const isRotate = args.includes('--rotate') || args.includes('-r');
|
|
1288
|
+
const isSaveEnv = args.includes('--env') || args.includes('-e');
|
|
1289
|
+
let serverId = targetServerId;
|
|
1290
|
+
if (serverId && serverId.startsWith('-')) {
|
|
1291
|
+
serverId = undefined;
|
|
1292
|
+
}
|
|
1293
|
+
if (!serverId) {
|
|
1294
|
+
const nonFlags = args.filter(a => !a.startsWith('-') && a !== 'receipt-secret' && a !== 'secret');
|
|
1295
|
+
if (nonFlags.length > 0) {
|
|
1296
|
+
serverId = nonFlags[0];
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
const creds = loadCredentials();
|
|
1300
|
+
const token = creds?.token || process.env.MCPAID_TOKEN;
|
|
1301
|
+
if (!token) {
|
|
1302
|
+
console.log('โ You must be logged in to access server receipt secrets.');
|
|
1303
|
+
console.log('Run: npx @mcpaid/sdk login');
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (!serverId) {
|
|
1307
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1308
|
+
serverId = await new Promise((res) => {
|
|
1309
|
+
rl.question('Enter the ID of the MCP server: ', (ans) => {
|
|
1310
|
+
rl.close();
|
|
1311
|
+
res(ans.trim());
|
|
1312
|
+
});
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
if (!serverId) {
|
|
1316
|
+
console.error('โ Error: Server ID is required. Example: npx @mcpaid/sdk server receipt-secret my-server');
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (isRotate) {
|
|
1320
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1321
|
+
const ans = await new Promise((res) => {
|
|
1322
|
+
rl.question(`โ ๏ธ Rotate Edge Receipt secret for "${serverId}"? (Previous secret remains valid for 1-hour grace window) (y/N): `, (a) => {
|
|
1323
|
+
rl.close();
|
|
1324
|
+
res(a.trim().toLowerCase());
|
|
1325
|
+
});
|
|
1326
|
+
});
|
|
1327
|
+
if (ans !== 'y' && ans !== 'yes') {
|
|
1328
|
+
console.log('Operation cancelled.');
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const endpoint = isRotate
|
|
1333
|
+
? `${gatewayUrl}/v1/servers/${encodeURIComponent(serverId)}/receipt-secret/rotate`
|
|
1334
|
+
: `${gatewayUrl}/v1/servers/${encodeURIComponent(serverId)}/receipt-secret`;
|
|
1335
|
+
const res = await fetch(endpoint, {
|
|
1336
|
+
method: isRotate ? 'POST' : 'GET',
|
|
1337
|
+
headers: {
|
|
1338
|
+
Authorization: `Bearer ${token}`,
|
|
1339
|
+
'Content-Type': 'application/json',
|
|
1340
|
+
},
|
|
1341
|
+
});
|
|
1342
|
+
const data = (await res.json().catch(() => ({})));
|
|
1343
|
+
if (!res.ok || !data.success) {
|
|
1344
|
+
console.error(`โ Failed to retrieve receipt secret: ${data.error || JSON.stringify(data)}`);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
console.log(`
|
|
1348
|
+
======================================================
|
|
1349
|
+
๐ MCPaid Edge Receipt Secret
|
|
1350
|
+
======================================================
|
|
1351
|
+
โข Server ID: ${data.serverId}
|
|
1352
|
+
โข Receipt Secret: ${data.receiptSecret}
|
|
1353
|
+
${data.previousReceiptSecret ? `โข Previous Secret: ${data.previousReceiptSecret} (1-hr grace window active)` : ''}
|
|
1354
|
+
======================================================
|
|
1355
|
+
๐ Downstream Backend Verification (TypeScript / Node):
|
|
1356
|
+
import { verifyEdgeReceipt } from '@mcpaid/sdk';
|
|
1357
|
+
|
|
1358
|
+
const err = await verifyEdgeReceipt(env, {
|
|
1359
|
+
receipt: request.headers.get('X-MCPaid-Receipt'),
|
|
1360
|
+
expectedTool: 'my_paid_tool',
|
|
1361
|
+
});
|
|
1362
|
+
======================================================
|
|
1363
|
+
`);
|
|
1364
|
+
if (isSaveEnv) {
|
|
1365
|
+
const envPath = resolve(process.cwd(), '.env');
|
|
1366
|
+
let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
|
|
1367
|
+
if (envContent.includes('MCPAID_RECEIPT_SECRET=')) {
|
|
1368
|
+
envContent = envContent.replace(/MCPAID_RECEIPT_SECRET=.*/g, `MCPAID_RECEIPT_SECRET=${data.receiptSecret}`);
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
envContent += `\n# MCPaid Edge Receipt Secret for backend verification\nMCPAID_RECEIPT_SECRET=${data.receiptSecret}\n`;
|
|
1372
|
+
}
|
|
1373
|
+
if (data.previousReceiptSecret) {
|
|
1374
|
+
if (envContent.includes('MCPAID_PREVIOUS_RECEIPT_SECRET=')) {
|
|
1375
|
+
envContent = envContent.replace(/MCPAID_PREVIOUS_RECEIPT_SECRET=.*/g, `MCPAID_PREVIOUS_RECEIPT_SECRET=${data.previousReceiptSecret}`);
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
envContent += `MCPAID_PREVIOUS_RECEIPT_SECRET=${data.previousReceiptSecret}\n`;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
writeFileSync(envPath, envContent, 'utf-8');
|
|
1382
|
+
console.log(`โ
Saved MCPAID_RECEIPT_SECRET to ${envPath}\n`);
|
|
1383
|
+
if (data.previousReceiptSecret) {
|
|
1384
|
+
console.log(`โน๏ธ Saved MCPAID_PREVIOUS_RECEIPT_SECRET (active for 1-hr grace period)`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1256
1388
|
async function handleServer() {
|
|
1257
1389
|
const sub = process.argv[3];
|
|
1258
1390
|
if (sub === 'list' || sub === 'ls') {
|
|
@@ -1264,13 +1396,17 @@ async function handleServer() {
|
|
|
1264
1396
|
if (sub === 'remove' || sub === 'rm' || sub === 'delete' || sub === 'disconnect') {
|
|
1265
1397
|
return handleServerRemove(process.argv[4]);
|
|
1266
1398
|
}
|
|
1399
|
+
if (sub === 'receipt-secret' || sub === 'secret') {
|
|
1400
|
+
return handleServerReceiptSecret(process.argv[4]);
|
|
1401
|
+
}
|
|
1267
1402
|
console.log(`
|
|
1268
1403
|
Usage: npx @mcpaid/sdk server <command>
|
|
1269
1404
|
|
|
1270
1405
|
Commands:
|
|
1271
|
-
add [config-path]
|
|
1272
|
-
list
|
|
1273
|
-
remove <server-id>
|
|
1406
|
+
add [config-path] Publish and monetize an MCP server on MCPaid Edge Gateway
|
|
1407
|
+
list List all MCP servers registered under your account
|
|
1408
|
+
remove <server-id> Disconnect and remove an MCP server from MCPaid network
|
|
1409
|
+
receipt-secret <server-id> Retrieve or rotate Edge Receipt secret for backend verification (--rotate, --env)
|
|
1274
1410
|
`);
|
|
1275
1411
|
}
|
|
1276
1412
|
async function handleWithdraw(amountArg) {
|