xyz-test-mcp 1.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/package.json +29 -0
- package/src/index.js +206 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xyz-test-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "GatePay Payment MCP Server - Pay 商户支付系统本地 MCP 服务",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"xyz-test-mcp": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
15
|
+
"zod": "^3.24.0"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"gatepay",
|
|
23
|
+
"payment"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import http from "node:http";
|
|
8
|
+
import https from "node:https";
|
|
9
|
+
|
|
10
|
+
// ============================================================
|
|
11
|
+
// GatePay API 客户端 — 内置签名逻辑,用户无需关心
|
|
12
|
+
// ============================================================
|
|
13
|
+
class GatePayClient {
|
|
14
|
+
constructor({ clientId, secretKey, authKey, baseUrl }) {
|
|
15
|
+
this.clientId = clientId;
|
|
16
|
+
this.secretKey = secretKey;
|
|
17
|
+
this.authKey = authKey;
|
|
18
|
+
this.baseUrl = baseUrl;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* HmacSHA512 签名
|
|
23
|
+
* payload = timestamp + '\n' + nonce + '\n' + requestBody + '\n'
|
|
24
|
+
*/
|
|
25
|
+
sign(body) {
|
|
26
|
+
const timestamp = Date.now().toString();
|
|
27
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
28
|
+
let nonce = "";
|
|
29
|
+
for (let i = 0; i < 32; i++) {
|
|
30
|
+
nonce += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
31
|
+
}
|
|
32
|
+
const payload = `${timestamp}\n${nonce}\n${body}\n`;
|
|
33
|
+
const signature = crypto
|
|
34
|
+
.createHmac("sha512", this.secretKey)
|
|
35
|
+
.update(payload)
|
|
36
|
+
.digest("hex");
|
|
37
|
+
return { timestamp, nonce, signature };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 调用 MCP JSON-RPC 接口
|
|
42
|
+
*/
|
|
43
|
+
async callTool(toolName, args) {
|
|
44
|
+
const body = JSON.stringify({
|
|
45
|
+
jsonrpc: "2.0",
|
|
46
|
+
id: crypto.randomUUID(),
|
|
47
|
+
method: "tools/call",
|
|
48
|
+
params: { name: toolName, arguments: args },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const { timestamp, nonce, signature } = this.sign(body);
|
|
52
|
+
const url = new URL(this.baseUrl);
|
|
53
|
+
const mod = url.protocol === "https:" ? https : http;
|
|
54
|
+
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const req = mod.request(
|
|
57
|
+
url,
|
|
58
|
+
{
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"X-GatePay-Certificate-ClientId": this.clientId,
|
|
63
|
+
"X-GatePay-Restricted-Key": this.authKey,
|
|
64
|
+
"X-GatePay-Timestamp": timestamp,
|
|
65
|
+
"X-GatePay-Nonce": nonce,
|
|
66
|
+
"X-GatePay-Signature": signature,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
(res) => {
|
|
70
|
+
let data = "";
|
|
71
|
+
res.on("data", (chunk) => (data += chunk));
|
|
72
|
+
res.on("end", () => {
|
|
73
|
+
try {
|
|
74
|
+
const json = JSON.parse(data);
|
|
75
|
+
// 从标准 MCP 响应中提取 content
|
|
76
|
+
const content = json?.result?.content;
|
|
77
|
+
if (content && content.length > 0) {
|
|
78
|
+
resolve(content[0].text);
|
|
79
|
+
} else if (json?.error) {
|
|
80
|
+
resolve(JSON.stringify({ error: json.error.message }));
|
|
81
|
+
} else {
|
|
82
|
+
resolve(data);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
resolve(data);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
req.on("error", (err) => reject(err));
|
|
91
|
+
req.write(body);
|
|
92
|
+
req.end();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================
|
|
98
|
+
// 从环境变量读取配置
|
|
99
|
+
// ============================================================
|
|
100
|
+
const CLIENT_ID = process.env.GATEPAY_CLIENT_ID;
|
|
101
|
+
const SECRET_KEY = process.env.GATEPAY_SECRET_KEY;
|
|
102
|
+
const AUTH_KEY = process.env.GATEPAY_RESTRICTED_KEY;
|
|
103
|
+
const BASE_URL = process.env.GATEPAY_BASE_URL || "https://openplatform.gateapi.io/payment/open/api/mcp";
|
|
104
|
+
|
|
105
|
+
if (!CLIENT_ID || !SECRET_KEY) {
|
|
106
|
+
console.error(
|
|
107
|
+
"错误:请设置环境变量 GATEPAY_CLIENT_ID 和 GATEPAY_SECRET_KEY"
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const client = new GatePayClient({
|
|
113
|
+
clientId: CLIENT_ID,
|
|
114
|
+
secretKey: SECRET_KEY,
|
|
115
|
+
authKey: AUTH_KEY,
|
|
116
|
+
baseUrl: BASE_URL,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ============================================================
|
|
120
|
+
// MCP Server
|
|
121
|
+
// ============================================================
|
|
122
|
+
const server = new McpServer({
|
|
123
|
+
name: "gatepay-merchant-mcp",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ---- 工具1:查询订单 ----
|
|
128
|
+
server.tool(
|
|
129
|
+
"payment_get",
|
|
130
|
+
"根据订单id查询订单详情",
|
|
131
|
+
{
|
|
132
|
+
prepay_id: z.string().optional().describe("订单ID,与 merchant_trade_no 二选一"),
|
|
133
|
+
merchant_trade_no: z.string().optional().describe("商户侧下单唯一id,与 prepay_id 二选一"),
|
|
134
|
+
},
|
|
135
|
+
async ({ prepay_id, merchant_trade_no }) => {
|
|
136
|
+
const text = await client.callTool("payment_get", { prepay_id, merchant_trade_no });
|
|
137
|
+
return { content: [{ type: "text", text }] };
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// ---- 工具2:查询退款 ----
|
|
142
|
+
server.tool(
|
|
143
|
+
"refunds_get",
|
|
144
|
+
"根据退款id查询退款详情",
|
|
145
|
+
{
|
|
146
|
+
refund_request_id: z.string().describe("发起退款时生成的唯一请求退款id"),
|
|
147
|
+
},
|
|
148
|
+
async ({ refund_request_id }) => {
|
|
149
|
+
const text = await client.callTool("refunds_get", { refund_request_id });
|
|
150
|
+
return { content: [{ type: "text", text }] };
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// ---- 工具3:查询余额 ----
|
|
155
|
+
server.tool(
|
|
156
|
+
"balances_get",
|
|
157
|
+
"查询商户账户余额,支持按币种筛选",
|
|
158
|
+
{
|
|
159
|
+
currencies: z.array(z.string()).optional().describe('要查询的币种列表,如 ["USDT","BTC"]'),
|
|
160
|
+
},
|
|
161
|
+
async ({ currencies }) => {
|
|
162
|
+
const text = await client.callTool("balances_get", { currencies });
|
|
163
|
+
return { content: [{ type: "text", text }] };
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// ---- 工具4:发起退款 ----
|
|
168
|
+
const REFUND_CHAIN_CODES = ["DOTSM", "GTEVM", "MATIC", "KAVAEVM", "APT", "ARBEVM", "OPETH", "EOS", "NEAR", "ALGO", "KAIA", "MON", "SOL", "TON", "BSC", "AVAX_C", "XPL", "ETH", "CELO", "TRX", "XTZ"];
|
|
169
|
+
server.tool(
|
|
170
|
+
"refunds_create",
|
|
171
|
+
[
|
|
172
|
+
"发起订单退款。使用流程:",
|
|
173
|
+
"1. 先询问用户退款金额(全额还是部分)",
|
|
174
|
+
"2. 询问退款方式:1 原路退,2 指定退",
|
|
175
|
+
"3. 如果指定退,询问渠道:Gate用户(需提供UID) 或 Web3(需提供地址)",
|
|
176
|
+
"4. 如果 Web3 退款,询问手续费承担方:1 商家 2 用户",
|
|
177
|
+
].join("\n"),
|
|
178
|
+
{
|
|
179
|
+
refundRequestId: z.string().describe("商户退款单号,需唯一"),
|
|
180
|
+
prepayId: z.string().describe("支付单id(平台订单号)"),
|
|
181
|
+
refundAmount: z.string().describe("退款金额字符串"),
|
|
182
|
+
refundStyle: z.number().describe("退款方式:1 原路退,2 指定退,当发起退款时,主动询问用户退款到哪里"),
|
|
183
|
+
refundReason: z.string().optional().describe("退款原因"),
|
|
184
|
+
refundGateId: z.string().optional().describe("退款 gate 侧 id(如有)"),
|
|
185
|
+
refundPayChannel: z.number().optional().describe("退款支付渠道:1 Gate 2 Web3,当refundStyle = 2时指定退时用户必须选择传递"),
|
|
186
|
+
refundToGateUid: z.number().optional().describe("退款至 Gate 用户 uid,当refundPayChannel=1时退款到Gate,用户必须输入uid"),
|
|
187
|
+
refundAddress: z.string().optional().describe("Web3 退款地址,当refundPayChannel=2时,必须用户提供地址"),
|
|
188
|
+
refundChain: z.enum(REFUND_CHAIN_CODES).optional().describe("退款网络,请向用户展示下列选项并只传入其中一项链代码"),
|
|
189
|
+
refundBearType: z.number().optional().describe("承担方:1 商家 2 用户,缺省为 1;退款方式为web3时refundPayChannel=2,需要用户选择费用承担方"),
|
|
190
|
+
memo: z.string().optional().describe("链上 memo"),
|
|
191
|
+
refundAmountTypeFull: z.number().optional().describe("1 全额 2 部分"),
|
|
192
|
+
refundCurrency: z.string().optional().describe("退款币种"),
|
|
193
|
+
refundFundStatementId: z.number().optional().describe("流水 id"),
|
|
194
|
+
refundSource: z.number().optional().describe("退款来源"),
|
|
195
|
+
},
|
|
196
|
+
async (args) => {
|
|
197
|
+
const text = await client.callTool("refunds_create", args);
|
|
198
|
+
return { content: [{ type: "text", text }] };
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// ============================================================
|
|
203
|
+
// 启动 stdio 传输
|
|
204
|
+
// ============================================================
|
|
205
|
+
const transport = new StdioServerTransport();
|
|
206
|
+
await server.connect(transport);
|