@cyberpay/nano-mcp 0.1.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/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # @cyberpay/nano-mcp
2
+
3
+ MCP Server for CyberNanoPay — 让 AI Agent 拥有零 Gas 纳米支付能力。
4
+
5
+ ## 为什么需要这个?
6
+
7
+ 传统支付方式无法满足 AI Agent 的高频微付需求:
8
+
9
+ | 收费模式 | 问题 |
10
+ |---------|------|
11
+ | 订阅制 | Agent 为了调 3 次 API 得买一个月,用量不可预测 |
12
+ | 积分/预付包 | 买 1000 次用了 50 次就不来了,剩余额度浪费 |
13
+ | 信用卡按次扣 | Stripe 最低手续费 $0.30/笔,$0.001 的 API 调用根本不可能按次收 |
14
+ | 链上直接付 | 每笔交易有 Gas 成本,高频场景(每秒几十次)不现实 |
15
+
16
+ CyberNanoPay 通过 offchain Ed25519 签名 + TEE 聚合 + 链上批量结算,让单笔支付成本趋近于零,$0.0001 的微付也经济可行。
17
+
18
+ **对 API 商家来说,这意味着除了订阅和积分包,多了一种之前技术上做不到的收费方式 — 真正的按次收费。**
19
+
20
+ ## 覆盖场景
21
+
22
+ NanoPay 解锁了这些之前"链上按次付不现实"的高频场景:
23
+
24
+ | 场景 | 单次价值 | 频率 | NanoPay 方案 |
25
+ |------|---------|------|-------------|
26
+ | LLM 推理 / 图片生成 | $0.01–$0.50 | 中频 | offchain 签名即时付,高价值场景 |
27
+ | RPC 节点 / 链上数据查询 | $0.0001 | 每秒几十次 | TEE 链下记账,批量结算 |
28
+ | AI Crawlers / RAG 检索 | $0.001/页 | 日均 10 万次 | x402 协议自动付费,定期结算 |
29
+ | IoT 传感器 / 充电桩 / WiFi | $0.0001–$0.01 | 每秒/每分钟 | offchain 签名累积,定时结算 |
30
+ | 付费文章 / 学术论文 | $0.10 | 低频 | 替代 $30 订阅,按篇付费 |
31
+ | 短视频微付 | $0.001 | 每次观看 | 用户无感,TEE 记账 |
32
+ | GPU 算力租用 | $0.01/秒 | 持续 | 按秒计费,批量结算 |
33
+ | 预言机喂价 / DEX 报价 | $0.001 | 高频 | 链上生态天然打通 |
34
+
35
+ ## 与 @ton/mcp 的协作
36
+
37
+ `@cyberpay/nano-mcp` 和 `@ton/mcp`(TON Agentic Wallets)是互补关系:
38
+
39
+ ```
40
+ @ton/mcp (链上操作层)
41
+ ├── 充值 USDT 到 CyberGateway 合约(一次性)
42
+ ├── 查链上余额、交易状态
43
+ ├── 提款、swap、NFT 操作
44
+ └── 管理 Agentic Wallet
45
+
46
+ @cyberpay/nano-mcp (高频微付层)
47
+ ├── offchain 签名支付(零 Gas,~1ms)
48
+ ├── TEE 验证 + 记账
49
+ ├── 批量结算到链上
50
+ └── TEE 签名收据 + Merkle 证明
51
+ ```
52
+
53
+ AI Agent 的完整支付流程:
54
+
55
+ ```
56
+ 1. Agent 通过 @ton/mcp → 充值 USDT 到 CyberGateway(链上,一次性)
57
+ 2. Agent 通过 @cyberpay/nano-mcp → offchain 签名付费(每次 API 调用,零 Gas)
58
+ 3. TEE 聚合器 → 验签、记账、累积批次
59
+ 4. TEE → 定期批量结算到链上(bilateral netting 压缩交易数)
60
+ 5. Agent 通过 @ton/mcp → 查余额、查结算状态、需要时再充值
61
+ ```
62
+
63
+ ## 安装
64
+
65
+ ```bash
66
+ npx @cyberpay/nano-mcp
67
+ ```
68
+
69
+ ## MCP 配置
70
+
71
+ 添加到 MCP 设置文件(`mcp.json`):
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "nano-pay": {
77
+ "command": "npx",
78
+ "args": ["@cyberpay/nano-mcp"],
79
+ "env": {
80
+ "NANO_TEE_URL": "https://tee.cyberpay.org"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ 搭配 TON Agentic Wallet 使用(推荐):
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "ton": {
93
+ "command": "npx",
94
+ "args": ["-y", "@ton/mcp@alpha"]
95
+ },
96
+ "nano-pay": {
97
+ "command": "npx",
98
+ "args": ["@cyberpay/nano-mcp"],
99
+ "env": {
100
+ "NANO_TEE_URL": "https://tee.cyberpay.org"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CyberNanoPay MCP Server
4
+ *
5
+ * Gives AI agents gas-free nanopayment capabilities on TON.
6
+ * Works alongside Agentic Wallets — agent uses agentic wallet for on-chain ops,
7
+ * and this MCP for high-frequency micropayments.
8
+ *
9
+ * Tools:
10
+ * nano_deposit — Deposit funds (simulated or on-chain)
11
+ * nano_pay — Sign and submit a micropayment
12
+ * nano_balance — Check balance
13
+ * nano_history — View payment history
14
+ * nano_receipt — Get a TEE-signed receipt
15
+ * nano_attestation — Verify TEE attestation
16
+ * nano_flush — Trigger batch settlement (settle pending payments on-chain)
17
+ * nano_withdraw — Request withdrawal (flush unsettled → prepare for on-chain withdraw)
18
+ * nano_policy — Get or set spending policy (limits, daily cap, HITL threshold)
19
+ */
20
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CyberNanoPay MCP Server
4
+ *
5
+ * Gives AI agents gas-free nanopayment capabilities on TON.
6
+ * Works alongside Agentic Wallets — agent uses agentic wallet for on-chain ops,
7
+ * and this MCP for high-frequency micropayments.
8
+ *
9
+ * Tools:
10
+ * nano_deposit — Deposit funds (simulated or on-chain)
11
+ * nano_pay — Sign and submit a micropayment
12
+ * nano_balance — Check balance
13
+ * nano_history — View payment history
14
+ * nano_receipt — Get a TEE-signed receipt
15
+ * nano_attestation — Verify TEE attestation
16
+ * nano_flush — Trigger batch settlement (settle pending payments on-chain)
17
+ * nano_withdraw — Request withdrawal (flush unsettled → prepare for on-chain withdraw)
18
+ * nano_policy — Get or set spending policy (limits, daily cap, HITL threshold)
19
+ */
20
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { z } from "zod";
23
+ import nacl from "tweetnacl";
24
+ import { createHash } from "crypto";
25
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
26
+ import { join } from "path";
27
+ import { homedir } from "os";
28
+ const TEE_URL = process.env.NANO_TEE_URL ?? "https://3c84244ec8585d9d81678e9f8933c2b63bbfe5cd-4030.dstack-pha-prod5.phala.network";
29
+ const ADMIN_TOKEN = process.env.NANO_ADMIN_TOKEN ?? "";
30
+ // ── Keypair management ──
31
+ // Agent generates or loads a keypair for signing payments
32
+ // Seed is persisted to ~/.cyberpay/agent-seed so address survives restarts
33
+ const SEED_DIR = join(homedir(), ".cyberpay");
34
+ const SEED_FILE = join(SEED_DIR, "agent-seed");
35
+ let agentKeypair = null;
36
+ function getOrCreateKeypair() {
37
+ if (agentKeypair)
38
+ return agentKeypair;
39
+ // Priority 1: env var
40
+ const envSeed = process.env.NANO_AGENT_SEED;
41
+ if (envSeed) {
42
+ agentKeypair = nacl.sign.keyPair.fromSeed(Buffer.from(envSeed, "hex"));
43
+ return agentKeypair;
44
+ }
45
+ // Priority 2: persisted seed file
46
+ try {
47
+ const saved = readFileSync(SEED_FILE, "utf-8").trim();
48
+ if (saved.length === 64) {
49
+ agentKeypair = nacl.sign.keyPair.fromSeed(Buffer.from(saved, "hex"));
50
+ return agentKeypair;
51
+ }
52
+ }
53
+ catch {
54
+ // File doesn't exist yet — will create below
55
+ }
56
+ // Priority 3: generate new and persist
57
+ const seed = nacl.randomBytes(32);
58
+ agentKeypair = nacl.sign.keyPair.fromSeed(seed);
59
+ try {
60
+ mkdirSync(SEED_DIR, { recursive: true });
61
+ writeFileSync(SEED_FILE, Buffer.from(seed).toString("hex"), { mode: 0o600 });
62
+ }
63
+ catch {
64
+ // Non-fatal: agent works but address changes on restart
65
+ }
66
+ return agentKeypair;
67
+ }
68
+ function toHex(bytes) {
69
+ return Buffer.from(bytes).toString("hex");
70
+ }
71
+ function agentAddress() {
72
+ const kp = getOrCreateKeypair();
73
+ return "0:" + toHex(kp.publicKey.slice(0, 32));
74
+ }
75
+ // ── Payment signing ──
76
+ function signPayment(to, amount, nonce, validBefore) {
77
+ const kp = getOrCreateKeypair();
78
+ const prefix = Buffer.from("CyberGateway:v1:");
79
+ const fromHash = Buffer.from(kp.publicKey.slice(0, 32));
80
+ // Parse "to" address hash
81
+ const toClean = to.startsWith("0:") ? to.slice(2) : to;
82
+ const toHash = Buffer.from(toClean.padEnd(64, "0").slice(0, 64), "hex");
83
+ const buf = Buffer.alloc(prefix.length + 32 + 32 + 16 + 8 + 32);
84
+ let offset = 0;
85
+ prefix.copy(buf, offset);
86
+ offset += prefix.length;
87
+ fromHash.copy(buf, offset);
88
+ offset += 32;
89
+ toHash.copy(buf, offset);
90
+ offset += 32;
91
+ const amountBuf = Buffer.alloc(16);
92
+ let amt = amount;
93
+ for (let i = 15; i >= 0; i--) {
94
+ amountBuf[i] = Number(amt & 0xffn);
95
+ amt >>= 8n;
96
+ }
97
+ amountBuf.copy(buf, offset);
98
+ offset += 16;
99
+ const timeBuf = Buffer.alloc(8);
100
+ let ts = BigInt(validBefore);
101
+ for (let i = 7; i >= 0; i--) {
102
+ timeBuf[i] = Number(ts & 0xffn);
103
+ ts >>= 8n;
104
+ }
105
+ timeBuf.copy(buf, offset);
106
+ offset += 8;
107
+ Buffer.from(nonce, "hex").copy(buf, offset);
108
+ const hash = createHash("sha256").update(buf).digest();
109
+ const sig = nacl.sign.detached(new Uint8Array(hash), kp.secretKey);
110
+ return toHex(sig);
111
+ }
112
+ // ── Helpers ──
113
+ async function teeRequest(path, options) {
114
+ const res = await fetch(`${TEE_URL}${path}`, options);
115
+ return res.json();
116
+ }
117
+ function adminHeaders() {
118
+ const h = { "Content-Type": "application/json" };
119
+ if (ADMIN_TOKEN)
120
+ h["Authorization"] = `Bearer ${ADMIN_TOKEN}`;
121
+ return h;
122
+ }
123
+ function randomHex(bytes) {
124
+ const buf = new Uint8Array(bytes);
125
+ crypto.getRandomValues(buf);
126
+ return toHex(buf);
127
+ }
128
+ // ── MCP Server ──
129
+ const server = new McpServer({
130
+ name: "nano-pay",
131
+ version: "0.1.0",
132
+ });
133
+ // Tool: nano_attestation
134
+ server.tool("nano_attestation", "Get TEE attestation report — proves the payment service runs in a secure enclave", {}, async () => {
135
+ const data = await teeRequest("/attestation");
136
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
137
+ });
138
+ // Tool: nano_balance
139
+ server.tool("nano_balance", "Check nanopay balance for an address", {
140
+ address: z.string().optional().describe("TON address to check. Defaults to agent's own address."),
141
+ }, async ({ address }) => {
142
+ const addr = address ?? agentAddress();
143
+ const data = await teeRequest(`/balance/${addr}`);
144
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
145
+ });
146
+ // Tool: nano_deposit
147
+ server.tool("nano_deposit", "Deposit funds into CyberNanoPay (simulated for testnet)", {
148
+ amount: z.string().describe("Amount in USDT units (6 decimals). e.g. '10000000' = $10"),
149
+ address: z.string().optional().describe("Depositor address. Defaults to agent's own address."),
150
+ }, async ({ amount, address }) => {
151
+ const addr = address ?? agentAddress();
152
+ const kp = getOrCreateKeypair();
153
+ // Register pubkey first
154
+ await teeRequest("/register-key", {
155
+ method: "POST",
156
+ headers: adminHeaders(),
157
+ body: JSON.stringify({ address: addr, publicKey: toHex(kp.publicKey) }),
158
+ });
159
+ // Simulate deposit
160
+ const data = await teeRequest("/simulate-deposit", {
161
+ method: "POST",
162
+ headers: adminHeaders(),
163
+ body: JSON.stringify({ address: addr, amount }),
164
+ });
165
+ return { content: [{ type: "text", text: `Deposited ${Number(amount) / 1e6} USDT. Balance: ${Number(data.balance) / 1e6} USDT` }] };
166
+ });
167
+ // Tool: nano_pay
168
+ server.tool("nano_pay", "Make a gas-free micropayment via TEE", {
169
+ to: z.string().describe("Recipient address"),
170
+ amount: z.string().describe("Amount in USDT units (6 decimals). e.g. '1000' = $0.001"),
171
+ }, async ({ to, amount }) => {
172
+ const from = agentAddress();
173
+ const nonce = randomHex(32);
174
+ const validBefore = Math.floor(Date.now() / 1000) + 300;
175
+ const signature = signPayment(to, BigInt(amount), nonce, validBefore);
176
+ const data = await teeRequest("/verify", {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json" },
179
+ body: JSON.stringify({ from, to, amount, validBefore, nonce, signature }),
180
+ });
181
+ if (data.success) {
182
+ return { content: [{ type: "text", text: `✓ Paid ${Number(amount) / 1e6} USDT to ${to}\nConfirmation: ${data.confirmationId}\nRemaining: ${Number(data.remainingBalance) / 1e6} USDT` }] };
183
+ }
184
+ return { content: [{ type: "text", text: `✗ Payment failed: ${data.error}` }] };
185
+ });
186
+ // Tool: nano_history
187
+ server.tool("nano_history", "View recent payment history", {
188
+ address: z.string().optional().describe("Address to check. Defaults to agent's own."),
189
+ limit: z.number().optional().describe("Max results (default 10)"),
190
+ }, async ({ address, limit }) => {
191
+ const addr = address ?? agentAddress();
192
+ const data = await teeRequest(`/history/payments/${addr}?limit=${limit ?? 10}`);
193
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
194
+ });
195
+ // Tool: nano_receipt
196
+ server.tool("nano_receipt", "Get a TEE-signed payment receipt", {
197
+ confirmationId: z.string().describe("Payment confirmation ID"),
198
+ }, async ({ confirmationId }) => {
199
+ const data = await teeRequest(`/receipt/${confirmationId}`);
200
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
201
+ });
202
+ // Tool: nano_stats
203
+ server.tool("nano_stats", "Get global protocol statistics", {}, async () => {
204
+ const data = await teeRequest("/stats");
205
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
206
+ });
207
+ // Tool: nano_flush
208
+ server.tool("nano_flush", "Trigger batch settlement — settles pending offchain payments on-chain. Use before withdrawing.", {}, async () => {
209
+ const data = await teeRequest("/flush", {
210
+ method: "POST",
211
+ headers: adminHeaders(),
212
+ });
213
+ if (data.settled) {
214
+ return { content: [{ type: "text", text: "✓ Batch settled on-chain successfully" }] };
215
+ }
216
+ return { content: [{ type: "text", text: `Flush result: ${JSON.stringify(data)}` }] };
217
+ });
218
+ // Tool: nano_withdraw
219
+ server.tool("nano_withdraw", "Request withdrawal — flushes unsettled payments for your address so funds can be withdrawn on-chain", {
220
+ address: z.string().optional().describe("Address to withdraw for. Defaults to agent's own."),
221
+ }, async ({ address }) => {
222
+ const addr = address ?? agentAddress();
223
+ const data = await teeRequest("/flush-for-withdraw", {
224
+ method: "POST",
225
+ headers: { "Content-Type": "application/json" },
226
+ body: JSON.stringify({ address: addr }),
227
+ });
228
+ if (data.error) {
229
+ return { content: [{ type: "text", text: `✗ Withdraw failed: ${data.error}` }] };
230
+ }
231
+ const balanceUsdt = Number(data.balance) / 1e6;
232
+ if (data.settled) {
233
+ return { content: [{ type: "text", text: `✓ Settlement complete. On-chain balance: ${balanceUsdt} USDT. You can now withdraw via the CyberGateway contract (InitiateWithdraw → wait cooldown → CompleteWithdraw).` }] };
234
+ }
235
+ return { content: [{ type: "text", text: `No pending payments to settle. Current balance: ${balanceUsdt} USDT. ${data.message ?? ""}` }] };
236
+ });
237
+ // Tool: nano_policy_get
238
+ server.tool("nano_policy_get", "Get spending policy for an address (limits, daily cap, HITL threshold)", {
239
+ address: z.string().optional().describe("Address to check. Defaults to agent's own."),
240
+ }, async ({ address }) => {
241
+ const addr = address ?? agentAddress();
242
+ const data = await teeRequest(`/policy/${addr}`);
243
+ if (!data.policy) {
244
+ return { content: [{ type: "text", text: `No spending policy set for ${addr}` }] };
245
+ }
246
+ const p = data.policy;
247
+ return { content: [{ type: "text", text: `Policy for ${addr}:\n Spending Limit: ${Number(p.spendingLimit) / 1e6} USDT per tx\n Daily Cap: ${Number(p.dailyCap) / 1e6} USDT/day\n HITL Threshold: ${Number(p.hitlThreshold) / 1e6} USDT (requires human approval above this)` }] };
248
+ });
249
+ // Tool: nano_policy_set
250
+ server.tool("nano_policy_set", "Set spending policy — per-transaction limit, daily cap, and HITL approval threshold", {
251
+ address: z.string().optional().describe("Address to set policy for. Defaults to agent's own."),
252
+ spendingLimit: z.string().describe("Max amount per transaction in USDT units (6 decimals). e.g. '5000000' = $5"),
253
+ dailyCap: z.string().describe("Max daily spending in USDT units. e.g. '50000000' = $50"),
254
+ hitlThreshold: z.string().describe("Payments above this require human approval. e.g. '1000000' = $1"),
255
+ }, async ({ address, spendingLimit, dailyCap, hitlThreshold }) => {
256
+ const addr = address ?? agentAddress();
257
+ const data = await teeRequest("/policy", {
258
+ method: "POST",
259
+ headers: adminHeaders(),
260
+ body: JSON.stringify({ address: addr, spendingLimit, dailyCap, hitlThreshold }),
261
+ });
262
+ if (data.success) {
263
+ return { content: [{ type: "text", text: `✓ Policy set for ${addr}:\n Spending Limit: ${Number(spendingLimit) / 1e6} USDT/tx\n Daily Cap: ${Number(dailyCap) / 1e6} USDT/day\n HITL Threshold: ${Number(hitlThreshold) / 1e6} USDT` }] };
264
+ }
265
+ return { content: [{ type: "text", text: `✗ Failed: ${data.error}` }] };
266
+ });
267
+ // ── Start ──
268
+ async function main() {
269
+ const transport = new StdioServerTransport();
270
+ await server.connect(transport);
271
+ }
272
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@cyberpay/nano-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for CyberNanoPay — gas-free nanopayments on TON for AI agents",
5
+ "type": "module",
6
+ "files": ["dist"],
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/CyberpayOrg/CyberNanoPay",
11
+ "directory": "mcp"
12
+ },
13
+ "keywords": ["mcp", "ton", "micropayments", "nanopay", "ai-agent", "claude", "cursor"],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "bin": {
18
+ "nano-mcp": "./dist/index.js"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "dev": "tsx src/index.ts"
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.0.0",
26
+ "tweetnacl": "^1.0.3",
27
+ "zod": "^3.22.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.0.0",
31
+ "tsx": "^4.19.0",
32
+ "typescript": "^5"
33
+ }
34
+ }