xstock-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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.js +827 -0
- package/package.json +27 -0
- package/src/data/binance.ts +87 -0
- package/src/data/coingecko.ts +113 -0
- package/src/data/tencent.ts +187 -0
- package/src/data/yahoo.ts +139 -0
- package/src/index.ts +7 -0
- package/src/server.ts +40 -0
- package/src/tools/crypto.ts +145 -0
- package/src/tools/index.ts +24 -0
- package/src/tools/kline.ts +91 -0
- package/src/tools/quotes.ts +52 -0
- package/src/tools/search.ts +54 -0
- package/src/tools/types.ts +44 -0
- package/src/tools/us-market.ts +73 -0
- package/src/utils/indicators.ts +0 -0
- package/src/utils/symbol.ts +0 -0
- package/test/data-binance.ts +68 -0
- package/test/data-coingecko.ts +71 -0
- package/test/data-tencent.ts +90 -0
- package/test/data-yahoo.ts +84 -0
- package/tsconfig.json +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/server.ts
|
|
27
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
+
var import_types6 = require("@modelcontextprotocol/sdk/types.js");
|
|
30
|
+
|
|
31
|
+
// src/tools/quotes.ts
|
|
32
|
+
var import_zod = require("zod");
|
|
33
|
+
|
|
34
|
+
// src/data/tencent.ts
|
|
35
|
+
var import_axios = __toESM(require("axios"));
|
|
36
|
+
var import_iconv_lite = __toESM(require("iconv-lite"));
|
|
37
|
+
function aQuoteUrl(code) {
|
|
38
|
+
return `https://qt.gtimg.cn/q=s_${resolveASharePrefix(code)}${code}`;
|
|
39
|
+
}
|
|
40
|
+
function hkQuoteUrl(code) {
|
|
41
|
+
return `https://qt.gtimg.cn/q=r_hk${code}`;
|
|
42
|
+
}
|
|
43
|
+
function aKlineUrl(code, period) {
|
|
44
|
+
return `https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=${resolveASharePrefix(code)}${code},${period},,,100,qfq`;
|
|
45
|
+
}
|
|
46
|
+
function hkKlineUrl(code, period) {
|
|
47
|
+
return `https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=hk${code},${period},,,100,qfq`;
|
|
48
|
+
}
|
|
49
|
+
function resolveASharePrefix(code) {
|
|
50
|
+
const c = code.replace(/^(sh|sz|bj)/i, "");
|
|
51
|
+
if (/^[56]/.test(c)) return "sh";
|
|
52
|
+
if (/^[89]/.test(c)) return "bj";
|
|
53
|
+
return "sz";
|
|
54
|
+
}
|
|
55
|
+
function periodParam(period) {
|
|
56
|
+
return { daily: "day", weekly: "week", monthly: "month" }[period];
|
|
57
|
+
}
|
|
58
|
+
function parseAShareQuote(raw, code) {
|
|
59
|
+
const match = raw.match(/v_s_\w+="([^"]+)"/);
|
|
60
|
+
if (!match) return null;
|
|
61
|
+
const f = match[1].split("~");
|
|
62
|
+
return {
|
|
63
|
+
code,
|
|
64
|
+
name: f[1],
|
|
65
|
+
price: parseFloat(f[3]),
|
|
66
|
+
change: parseFloat(f[4]),
|
|
67
|
+
changePercent: f[5] + "%",
|
|
68
|
+
volume: f[6],
|
|
69
|
+
marketCap: f[9]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseHKQuote(raw, code) {
|
|
73
|
+
const match = raw.match(/v_r_hk\w+="([^"]+)"/);
|
|
74
|
+
if (!match) return null;
|
|
75
|
+
const f = match[1].split("~");
|
|
76
|
+
return {
|
|
77
|
+
code,
|
|
78
|
+
name: f[1],
|
|
79
|
+
price: parseFloat(f[3]),
|
|
80
|
+
prevClose: parseFloat(f[4]),
|
|
81
|
+
open: parseFloat(f[5]),
|
|
82
|
+
volume: f[6],
|
|
83
|
+
time: f[30],
|
|
84
|
+
change: parseFloat(f[31]),
|
|
85
|
+
changePercent: f[32] + "%",
|
|
86
|
+
high: parseFloat(f[33]),
|
|
87
|
+
low: parseFloat(f[34])
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parseKline(data, key) {
|
|
91
|
+
const d = data;
|
|
92
|
+
const dataMap = d?.["data"];
|
|
93
|
+
const stockData = dataMap?.[key] ?? {};
|
|
94
|
+
const raw = stockData?.["qfqday"] ?? stockData?.["day"] ?? [];
|
|
95
|
+
return raw.slice(-100).map((k) => ({
|
|
96
|
+
date: k[0],
|
|
97
|
+
open: parseFloat(k[1]),
|
|
98
|
+
close: parseFloat(k[2]),
|
|
99
|
+
high: parseFloat(k[3]),
|
|
100
|
+
low: parseFloat(k[4]),
|
|
101
|
+
volume: parseFloat(k[5])
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
async function fetchAShareQuotes(codes) {
|
|
105
|
+
process.stderr.write(`[tencent] fetchAShareQuotes codes=${codes.join(",")}
|
|
106
|
+
`);
|
|
107
|
+
const results = await Promise.all(
|
|
108
|
+
codes.map(async (raw) => {
|
|
109
|
+
const code = raw.replace(/^(sh|sz|bj)/i, "");
|
|
110
|
+
try {
|
|
111
|
+
const res = await import_axios.default.get(aQuoteUrl(code), { timeout: 5e3, responseType: "arraybuffer" });
|
|
112
|
+
const text = import_iconv_lite.default.decode(Buffer.from(res.data), "gbk");
|
|
113
|
+
const quote = parseAShareQuote(text, code);
|
|
114
|
+
if (!quote) process.stderr.write(`[tencent] A\u80A1\u89E3\u6790\u5931\u8D25 code=${code}
|
|
115
|
+
`);
|
|
116
|
+
return quote;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
process.stderr.write(`[tencent] A\u80A1\u8BF7\u6C42\u5931\u8D25 code=${code} err=${e}
|
|
119
|
+
`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
return results.filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
async function fetchHKQuotes(codes) {
|
|
127
|
+
process.stderr.write(`[tencent] fetchHKQuotes codes=${codes.join(",")}
|
|
128
|
+
`);
|
|
129
|
+
const results = await Promise.all(
|
|
130
|
+
codes.map(async (raw) => {
|
|
131
|
+
const code = raw.replace(/^hk/i, "").padStart(5, "0");
|
|
132
|
+
try {
|
|
133
|
+
const res = await import_axios.default.get(hkQuoteUrl(code), { timeout: 5e3, responseType: "arraybuffer" });
|
|
134
|
+
const text = import_iconv_lite.default.decode(Buffer.from(res.data), "gbk");
|
|
135
|
+
const quote = parseHKQuote(text, code);
|
|
136
|
+
if (!quote) process.stderr.write(`[tencent] \u6E2F\u80A1\u89E3\u6790\u5931\u8D25 code=${code}
|
|
137
|
+
`);
|
|
138
|
+
return quote;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
process.stderr.write(`[tencent] \u6E2F\u80A1\u8BF7\u6C42\u5931\u8D25 code=${code} err=${e}
|
|
141
|
+
`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
return results.filter(Boolean);
|
|
147
|
+
}
|
|
148
|
+
async function fetchAShareKline(code, period = "daily") {
|
|
149
|
+
const stripped = code.replace(/^(sh|sz|bj)/i, "");
|
|
150
|
+
const prefix = resolveASharePrefix(stripped);
|
|
151
|
+
process.stderr.write(`[tencent] fetchAShareKline code=${stripped} period=${period}
|
|
152
|
+
`);
|
|
153
|
+
const res = await import_axios.default.get(aKlineUrl(stripped, periodParam(period)), { timeout: 5e3 });
|
|
154
|
+
const bars = parseKline(res.data, `${prefix}${stripped}`);
|
|
155
|
+
process.stderr.write(`[tencent] fetchAShareKline \u8FD4\u56DE ${bars.length} \u6761
|
|
156
|
+
`);
|
|
157
|
+
return bars;
|
|
158
|
+
}
|
|
159
|
+
async function fetchHKKline(code, period = "daily") {
|
|
160
|
+
const normalized = code.replace(/^hk/i, "").padStart(5, "0");
|
|
161
|
+
process.stderr.write(`[tencent] fetchHKKline code=${normalized} period=${period}
|
|
162
|
+
`);
|
|
163
|
+
const res = await import_axios.default.get(hkKlineUrl(normalized, periodParam(period)), { timeout: 5e3 });
|
|
164
|
+
const bars = parseKline(res.data, `hk${normalized}`);
|
|
165
|
+
process.stderr.write(`[tencent] fetchHKKline \u8FD4\u56DE ${bars.length} \u6761
|
|
166
|
+
`);
|
|
167
|
+
return bars;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/data/yahoo.ts
|
|
171
|
+
var import_yahoo_finance2 = __toESM(require("yahoo-finance2"));
|
|
172
|
+
var yf = new import_yahoo_finance2.default({ suppressNotices: ["yahooSurvey"] });
|
|
173
|
+
async function fetchUSQuotes(symbols) {
|
|
174
|
+
process.stderr.write(`[yahoo] fetchUSQuotes symbols=${symbols.join(",")}
|
|
175
|
+
`);
|
|
176
|
+
const results = await Promise.all(
|
|
177
|
+
symbols.map(async (symbol) => {
|
|
178
|
+
try {
|
|
179
|
+
const q = await yf.quote(symbol);
|
|
180
|
+
const result = {
|
|
181
|
+
symbol: q.symbol,
|
|
182
|
+
name: q.longName ?? q.shortName ?? symbol,
|
|
183
|
+
price: q.regularMarketPrice ?? 0,
|
|
184
|
+
change: q.regularMarketChange ?? 0,
|
|
185
|
+
changePercent: (q.regularMarketChangePercent ?? 0).toFixed(2) + "%",
|
|
186
|
+
open: q.regularMarketOpen ?? 0,
|
|
187
|
+
high: q.regularMarketDayHigh ?? 0,
|
|
188
|
+
low: q.regularMarketDayLow ?? 0,
|
|
189
|
+
prevClose: q.regularMarketPreviousClose ?? 0,
|
|
190
|
+
volume: q.regularMarketVolume ?? 0,
|
|
191
|
+
marketCap: q.marketCap ?? null,
|
|
192
|
+
currency: q.currency ?? "USD",
|
|
193
|
+
exchange: q.fullExchangeName ?? q.exchange ?? ""
|
|
194
|
+
};
|
|
195
|
+
process.stderr.write(`[yahoo] ${symbol} price=${result.price}
|
|
196
|
+
`);
|
|
197
|
+
return result;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
process.stderr.write(`[yahoo] \u8BF7\u6C42\u5931\u8D25 symbol=${symbol} err=${e}
|
|
200
|
+
`);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
return results.filter(Boolean);
|
|
206
|
+
}
|
|
207
|
+
async function fetchUSKlines(symbol, startDate, endDate, interval = "1d") {
|
|
208
|
+
process.stderr.write(`[yahoo] fetchUSKlines symbol=${symbol} start=${startDate} end=${endDate} interval=${interval}
|
|
209
|
+
`);
|
|
210
|
+
const rows = await yf.historical(symbol, {
|
|
211
|
+
period1: startDate,
|
|
212
|
+
period2: endDate,
|
|
213
|
+
interval
|
|
214
|
+
});
|
|
215
|
+
const bars = rows.map((r) => ({
|
|
216
|
+
date: r.date.toISOString().slice(0, 10),
|
|
217
|
+
open: r.open ?? 0,
|
|
218
|
+
high: r.high ?? 0,
|
|
219
|
+
low: r.low ?? 0,
|
|
220
|
+
close: r.close ?? 0,
|
|
221
|
+
volume: r.volume ?? 0,
|
|
222
|
+
adjClose: r.adjClose ?? null
|
|
223
|
+
}));
|
|
224
|
+
process.stderr.write(`[yahoo] fetchUSKlines \u8FD4\u56DE ${bars.length} \u6761
|
|
225
|
+
`);
|
|
226
|
+
return bars;
|
|
227
|
+
}
|
|
228
|
+
async function fetchStockProfile(symbol) {
|
|
229
|
+
process.stderr.write(`[yahoo] fetchStockProfile symbol=${symbol}
|
|
230
|
+
`);
|
|
231
|
+
const summary = await yf.quoteSummary(symbol, {
|
|
232
|
+
modules: ["price", "summaryProfile", "defaultKeyStatistics"]
|
|
233
|
+
});
|
|
234
|
+
const price = summary.price;
|
|
235
|
+
const profile = summary.summaryProfile;
|
|
236
|
+
const stats = summary.defaultKeyStatistics;
|
|
237
|
+
const result = {
|
|
238
|
+
symbol,
|
|
239
|
+
name: price?.longName ?? price?.shortName ?? symbol,
|
|
240
|
+
sector: profile?.sector ?? null,
|
|
241
|
+
industry: profile?.industry ?? null,
|
|
242
|
+
description: profile?.longBusinessSummary ?? null,
|
|
243
|
+
website: profile?.website ?? null,
|
|
244
|
+
employees: profile?.fullTimeEmployees ?? null,
|
|
245
|
+
country: profile?.country ?? null,
|
|
246
|
+
currency: price?.currency ?? "USD",
|
|
247
|
+
marketCap: price?.marketCap ?? null,
|
|
248
|
+
pe: price?.trailingPE ?? null,
|
|
249
|
+
eps: stats?.trailingEps ?? null,
|
|
250
|
+
beta: stats?.beta ?? null,
|
|
251
|
+
fiftyTwoWeekHigh: price?.fiftyTwoWeekHigh ?? null,
|
|
252
|
+
fiftyTwoWeekLow: price?.fiftyTwoWeekLow ?? null
|
|
253
|
+
};
|
|
254
|
+
process.stderr.write(`[yahoo] fetchStockProfile ${symbol} sector=${result.sector}
|
|
255
|
+
`);
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/data/binance.ts
|
|
260
|
+
var import_axios2 = __toESM(require("axios"));
|
|
261
|
+
function normalizeSymbol(symbol) {
|
|
262
|
+
const s = symbol.toUpperCase();
|
|
263
|
+
return s.endsWith("USDT") ? s : s + "USDT";
|
|
264
|
+
}
|
|
265
|
+
async function fetchCryptoTickers(symbols) {
|
|
266
|
+
process.stderr.write(`[binance] fetchCryptoTickers symbols=${symbols.join(",")}
|
|
267
|
+
`);
|
|
268
|
+
const results = await Promise.all(
|
|
269
|
+
symbols.map(async (raw) => {
|
|
270
|
+
const sym = normalizeSymbol(raw);
|
|
271
|
+
try {
|
|
272
|
+
const res = await import_axios2.default.get(
|
|
273
|
+
`https://api.binance.com/api/v3/ticker/24hr?symbol=${sym}`,
|
|
274
|
+
{ timeout: 5e3 }
|
|
275
|
+
);
|
|
276
|
+
const d = res.data;
|
|
277
|
+
process.stderr.write(`[binance] ${sym} price=${d.lastPrice}
|
|
278
|
+
`);
|
|
279
|
+
return {
|
|
280
|
+
symbol: sym,
|
|
281
|
+
price: parseFloat(d.lastPrice),
|
|
282
|
+
change: parseFloat(d.priceChange),
|
|
283
|
+
changePercent: parseFloat(d.priceChangePercent).toFixed(2) + "%",
|
|
284
|
+
high: parseFloat(d.highPrice),
|
|
285
|
+
low: parseFloat(d.lowPrice),
|
|
286
|
+
volume: parseFloat(d.volume),
|
|
287
|
+
quoteVolume: parseFloat(d.quoteVolume)
|
|
288
|
+
};
|
|
289
|
+
} catch (e) {
|
|
290
|
+
process.stderr.write(`[binance] \u8BF7\u6C42\u5931\u8D25 symbol=${sym} err=${e}
|
|
291
|
+
`);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
return results.filter(Boolean);
|
|
297
|
+
}
|
|
298
|
+
async function fetchCryptoKlines(symbol, interval = "1d", limit = 100) {
|
|
299
|
+
const sym = normalizeSymbol(symbol);
|
|
300
|
+
process.stderr.write(`[binance] fetchCryptoKlines symbol=${sym} interval=${interval} limit=${limit}
|
|
301
|
+
`);
|
|
302
|
+
const res = await import_axios2.default.get(
|
|
303
|
+
`https://api.binance.com/api/v3/klines?symbol=${sym}&interval=${interval}&limit=${limit}`,
|
|
304
|
+
{ timeout: 5e3 }
|
|
305
|
+
);
|
|
306
|
+
const bars = res.data.map((k) => ({
|
|
307
|
+
date: new Date(k[0]).toISOString().slice(0, 10),
|
|
308
|
+
open: parseFloat(k[1]),
|
|
309
|
+
high: parseFloat(k[2]),
|
|
310
|
+
low: parseFloat(k[3]),
|
|
311
|
+
close: parseFloat(k[4]),
|
|
312
|
+
volume: parseFloat(k[5])
|
|
313
|
+
}));
|
|
314
|
+
process.stderr.write(`[binance] fetchCryptoKlines \u8FD4\u56DE ${bars.length} \u6761
|
|
315
|
+
`);
|
|
316
|
+
return bars;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/tools/types.ts
|
|
320
|
+
function ok(data) {
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function err(message) {
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
328
|
+
isError: true
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/tools/quotes.ts
|
|
333
|
+
var Input = import_zod.z.object({
|
|
334
|
+
market: import_zod.z.enum(["a-share", "hk", "us", "crypto"]),
|
|
335
|
+
codes: import_zod.z.array(import_zod.z.string()).min(1).max(20)
|
|
336
|
+
});
|
|
337
|
+
var getQuoteTool = {
|
|
338
|
+
tool: {
|
|
339
|
+
name: "get_quote",
|
|
340
|
+
description: "\u83B7\u53D6\u80A1\u7968\u6216\u52A0\u5BC6\u8D27\u5E01\u5B9E\u65F6\u884C\u60C5\u3002\u652F\u6301\u7F8E\u80A1\uFF08us\uFF09\u3001A\u80A1\uFF08a-share\uFF09\u3001\u6E2F\u80A1\uFF08hk\uFF09\u3001\u52A0\u5BC6\u8D27\u5E01\uFF08crypto\uFF09\u3002\u7F8E\u80A1\u793A\u4F8B\uFF1Acodes=['AAPL','NVDA']\uFF1BA\u80A1\uFF1Acodes=['600519','000858']\uFF1B\u6E2F\u80A1\uFF1Acodes=['00700','09988']\uFF1B\u52A0\u5BC6\uFF1Acodes=['BTC','ETH']\u3002",
|
|
341
|
+
inputSchema: {
|
|
342
|
+
type: "object",
|
|
343
|
+
properties: {
|
|
344
|
+
market: {
|
|
345
|
+
type: "string",
|
|
346
|
+
enum: ["a-share", "hk", "us", "crypto"],
|
|
347
|
+
description: "\u5E02\u573A\u7C7B\u578B\uFF1Aa-share=A\u80A1, hk=\u6E2F\u80A1, us=\u7F8E\u80A1, crypto=\u52A0\u5BC6\u8D27\u5E01"
|
|
348
|
+
},
|
|
349
|
+
codes: {
|
|
350
|
+
type: "array",
|
|
351
|
+
items: { type: "string" },
|
|
352
|
+
description: "\u80A1\u7968/\u5E01\u79CD\u4EE3\u7801\u5217\u8868\uFF0C\u6700\u591A20\u4E2A",
|
|
353
|
+
minItems: 1,
|
|
354
|
+
maxItems: 20
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
required: ["market", "codes"]
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
handler: async (input) => {
|
|
361
|
+
const parsed = Input.safeParse(input);
|
|
362
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
363
|
+
const { market, codes } = parsed.data;
|
|
364
|
+
try {
|
|
365
|
+
if (market === "a-share") return ok(await fetchAShareQuotes(codes));
|
|
366
|
+
if (market === "hk") return ok(await fetchHKQuotes(codes));
|
|
367
|
+
if (market === "us") return ok(await fetchUSQuotes(codes));
|
|
368
|
+
return ok(await fetchCryptoTickers(codes));
|
|
369
|
+
} catch (e) {
|
|
370
|
+
return err(e.message);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/tools/kline.ts
|
|
376
|
+
var import_zod2 = require("zod");
|
|
377
|
+
var periodToYahoo = {
|
|
378
|
+
daily: "1d",
|
|
379
|
+
weekly: "1wk",
|
|
380
|
+
monthly: "1mo"
|
|
381
|
+
};
|
|
382
|
+
var Input2 = import_zod2.z.object({
|
|
383
|
+
market: import_zod2.z.enum(["a-share", "hk", "us", "crypto"]),
|
|
384
|
+
code: import_zod2.z.string(),
|
|
385
|
+
period: import_zod2.z.enum(["daily", "weekly", "monthly"]).optional(),
|
|
386
|
+
start: import_zod2.z.string().optional(),
|
|
387
|
+
end: import_zod2.z.string().optional(),
|
|
388
|
+
interval: import_zod2.z.string().optional(),
|
|
389
|
+
limit: import_zod2.z.number().int().min(1).max(500).optional()
|
|
390
|
+
});
|
|
391
|
+
var getKlineTool = {
|
|
392
|
+
tool: {
|
|
393
|
+
name: "get_kline",
|
|
394
|
+
description: "\u83B7\u53D6K\u7EBF\u6570\u636E\uFF08OHLCV\uFF09\u3002\u80A1\u7968\u652F\u6301 daily/weekly/monthly\uFF1B\u52A0\u5BC6\u8D27\u5E01\u652F\u6301 1m/5m/15m/1h/4h/1d/1w \u7B49 Binance \u6807\u51C6\u5468\u671F\u3002\u7F8E\u80A1\u53EF\u6307\u5B9A start/end \u65E5\u671F\uFF08YYYY-MM-DD\uFF09\uFF1B\u52A0\u5BC6\u53EF\u6307\u5B9A limit\uFF08\u6700\u591A500\u6761\uFF09\u3002",
|
|
395
|
+
inputSchema: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: {
|
|
398
|
+
market: {
|
|
399
|
+
type: "string",
|
|
400
|
+
enum: ["a-share", "hk", "us", "crypto"],
|
|
401
|
+
description: "\u5E02\u573A\u7C7B\u578B"
|
|
402
|
+
},
|
|
403
|
+
code: {
|
|
404
|
+
type: "string",
|
|
405
|
+
description: "\u4EE3\u7801\uFF0C\u5982 AAPL\u3001600519\u300100700\u3001BTC"
|
|
406
|
+
},
|
|
407
|
+
period: {
|
|
408
|
+
type: "string",
|
|
409
|
+
enum: ["daily", "weekly", "monthly"],
|
|
410
|
+
description: "\u5468\u671F\uFF08\u80A1\u7968\u5E02\u573A\uFF09\uFF0C\u9ED8\u8BA4 daily"
|
|
411
|
+
},
|
|
412
|
+
start: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4EC5\u7F8E\u80A1\uFF0C\u9ED8\u8BA4\u8FD190\u5929\uFF09"
|
|
415
|
+
},
|
|
416
|
+
end: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u4EC5\u7F8E\u80A1\uFF0C\u9ED8\u8BA4\u4ECA\u5929\uFF09"
|
|
419
|
+
},
|
|
420
|
+
interval: {
|
|
421
|
+
type: "string",
|
|
422
|
+
description: "\u52A0\u5BC6\u8D27\u5E01\u5468\u671F\uFF0C\u5982 1d\u30014h\u30011h\u300115m\uFF0C\u9ED8\u8BA4 1d"
|
|
423
|
+
},
|
|
424
|
+
limit: {
|
|
425
|
+
type: "number",
|
|
426
|
+
description: "\u52A0\u5BC6\u8D27\u5E01\u8FD4\u56DE\u6761\u6570\uFF0C\u6700\u5927500\uFF0C\u9ED8\u8BA4100"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
required: ["market", "code"]
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
handler: async (input) => {
|
|
433
|
+
const parsed = Input2.safeParse(input);
|
|
434
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
435
|
+
const { market, code, period, start, end, interval, limit } = parsed.data;
|
|
436
|
+
try {
|
|
437
|
+
if (market === "a-share") {
|
|
438
|
+
return ok(await fetchAShareKline(code, period ?? "daily"));
|
|
439
|
+
}
|
|
440
|
+
if (market === "hk") {
|
|
441
|
+
return ok(await fetchHKKline(code, period ?? "daily"));
|
|
442
|
+
}
|
|
443
|
+
if (market === "us") {
|
|
444
|
+
const endDate = end ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
445
|
+
const startDate = start ?? new Date(Date.now() - 90 * 864e5).toISOString().slice(0, 10);
|
|
446
|
+
const yInterval = periodToYahoo[period ?? "daily"] ?? "1d";
|
|
447
|
+
return ok(await fetchUSKlines(code, startDate, endDate, yInterval));
|
|
448
|
+
}
|
|
449
|
+
return ok(await fetchCryptoKlines(code, interval ?? "1d", limit ?? 100));
|
|
450
|
+
} catch (e) {
|
|
451
|
+
return err(e.message);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/tools/search.ts
|
|
457
|
+
var import_zod3 = require("zod");
|
|
458
|
+
var import_yahoo_finance22 = __toESM(require("yahoo-finance2"));
|
|
459
|
+
var yf2 = new import_yahoo_finance22.default({ suppressNotices: ["yahooSurvey"] });
|
|
460
|
+
var Input3 = import_zod3.z.object({
|
|
461
|
+
query: import_zod3.z.string().min(1),
|
|
462
|
+
limit: import_zod3.z.number().int().min(1).max(20).optional()
|
|
463
|
+
});
|
|
464
|
+
var searchStockTool = {
|
|
465
|
+
tool: {
|
|
466
|
+
name: "search_stock",
|
|
467
|
+
description: "\u6309\u5173\u952E\u8BCD\u641C\u7D22\u80A1\u7968\u6216ETF\uFF0C\u8FD4\u56DE\u5339\u914D\u7684\u80A1\u7968\u5217\u8868\uFF08\u542B\u4EE3\u7801\u3001\u540D\u79F0\u3001\u4EA4\u6613\u6240\u3001\u7C7B\u578B\uFF09\u3002\u9002\u7528\u4E8E\u4E0D\u77E5\u9053\u786E\u5207\u4EE3\u7801\u65F6\u67E5\u627E\u6807\u7684\u3002",
|
|
468
|
+
inputSchema: {
|
|
469
|
+
type: "object",
|
|
470
|
+
properties: {
|
|
471
|
+
query: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5982\u516C\u53F8\u540D\u6216\u4EE3\u7801\uFF0C\u5982 'Apple'\u3001'TSLA'\u3001'nvidia'"
|
|
474
|
+
},
|
|
475
|
+
limit: {
|
|
476
|
+
type: "number",
|
|
477
|
+
description: "\u6700\u591A\u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA410\uFF0C\u6700\u592720"
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
required: ["query"]
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
handler: async (input) => {
|
|
484
|
+
const parsed = Input3.safeParse(input);
|
|
485
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
486
|
+
const { query, limit = 10 } = parsed.data;
|
|
487
|
+
try {
|
|
488
|
+
const res = await yf2.search(query);
|
|
489
|
+
const items = (res.quotes ?? []).slice(0, limit).map((q) => {
|
|
490
|
+
const r = q;
|
|
491
|
+
return {
|
|
492
|
+
symbol: r.symbol,
|
|
493
|
+
name: r.longname ?? r.shortname ?? "",
|
|
494
|
+
exchange: r.exchange ?? "",
|
|
495
|
+
type: r.typeDisp ?? r.quoteType ?? ""
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
return ok(items);
|
|
499
|
+
} catch (e) {
|
|
500
|
+
return err(e.message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/tools/us-market.ts
|
|
506
|
+
var import_zod4 = require("zod");
|
|
507
|
+
var US_INDICES = ["^GSPC", "^IXIC", "^DJI", "^RUT", "^VIX"];
|
|
508
|
+
var INDEX_NAMES = {
|
|
509
|
+
"^GSPC": "S&P 500",
|
|
510
|
+
"^IXIC": "Nasdaq Composite",
|
|
511
|
+
"^DJI": "Dow Jones",
|
|
512
|
+
"^RUT": "Russell 2000",
|
|
513
|
+
"^VIX": "VIX \u6050\u614C\u6307\u6570"
|
|
514
|
+
};
|
|
515
|
+
var getUsIndicesTool = {
|
|
516
|
+
tool: {
|
|
517
|
+
name: "get_us_indices",
|
|
518
|
+
description: "\u83B7\u53D6\u7F8E\u80A1\u4E3B\u8981\u6307\u6570\u5B9E\u65F6\u884C\u60C5\uFF0C\u5305\u62EC\u6807\u666E500\u3001\u7EB3\u65AF\u8FBE\u514B\u3001\u9053\u743C\u65AF\u3001\u7F57\u7D202000\u3001VIX\u6050\u614C\u6307\u6570\u3002",
|
|
519
|
+
inputSchema: {
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: {},
|
|
522
|
+
required: []
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
handler: async () => {
|
|
526
|
+
try {
|
|
527
|
+
const quotes = await fetchUSQuotes(US_INDICES);
|
|
528
|
+
const result = quotes.map((q) => ({
|
|
529
|
+
...q,
|
|
530
|
+
indexName: INDEX_NAMES[q.symbol] ?? q.symbol
|
|
531
|
+
}));
|
|
532
|
+
return ok(result);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
return err(e.message);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
var ProfileInput = import_zod4.z.object({
|
|
539
|
+
symbol: import_zod4.z.string().min(1)
|
|
540
|
+
});
|
|
541
|
+
var getStockProfileTool = {
|
|
542
|
+
tool: {
|
|
543
|
+
name: "get_stock_profile",
|
|
544
|
+
description: "\u83B7\u53D6\u7F8E\u80A1\u4E0A\u5E02\u516C\u53F8\u57FA\u672C\u9762\u4FE1\u606F\uFF1A\u884C\u4E1A\u3001\u5E02\u503C\u3001PE\u3001EPS\u3001Beta\u300152\u5468\u9AD8\u4F4E\u70B9\u3001\u5458\u5DE5\u6570\u3001\u516C\u53F8\u7B80\u4ECB\u7B49\u3002",
|
|
545
|
+
inputSchema: {
|
|
546
|
+
type: "object",
|
|
547
|
+
properties: {
|
|
548
|
+
symbol: {
|
|
549
|
+
type: "string",
|
|
550
|
+
description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001TSLA\u3001NVDA"
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
required: ["symbol"]
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
handler: async (input) => {
|
|
557
|
+
const parsed = ProfileInput.safeParse(input);
|
|
558
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
559
|
+
const { symbol } = parsed.data;
|
|
560
|
+
try {
|
|
561
|
+
const profile = await fetchStockProfile(symbol);
|
|
562
|
+
return ok(profile);
|
|
563
|
+
} catch (e) {
|
|
564
|
+
return err(e.message);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// src/tools/crypto.ts
|
|
570
|
+
var import_zod5 = require("zod");
|
|
571
|
+
var import_axios4 = __toESM(require("axios"));
|
|
572
|
+
|
|
573
|
+
// src/data/coingecko.ts
|
|
574
|
+
var import_axios3 = __toESM(require("axios"));
|
|
575
|
+
var BASE = "https://api.coingecko.com/api/v3";
|
|
576
|
+
var FEAR_GREED_URL = "https://api.alternative.me/fng/?limit=1";
|
|
577
|
+
async function fetchGlobalMarket() {
|
|
578
|
+
process.stderr.write(`[coingecko] fetchGlobalMarket
|
|
579
|
+
`);
|
|
580
|
+
const res = await import_axios3.default.get(`${BASE}/global`, { timeout: 8e3 });
|
|
581
|
+
const d = res.data.data;
|
|
582
|
+
const result = {
|
|
583
|
+
totalMarketCapUsd: d.total_market_cap?.usd ?? 0,
|
|
584
|
+
totalVolumeUsd: d.total_volume?.usd ?? 0,
|
|
585
|
+
btcDominance: parseFloat(d.market_cap_percentage?.btc?.toFixed(2) ?? "0"),
|
|
586
|
+
ethDominance: parseFloat(d.market_cap_percentage?.eth?.toFixed(2) ?? "0"),
|
|
587
|
+
activeCryptos: d.active_cryptocurrencies ?? 0,
|
|
588
|
+
marketCapChangePercent24h: parseFloat(d.market_cap_change_percentage_24h_usd?.toFixed(2) ?? "0")
|
|
589
|
+
};
|
|
590
|
+
process.stderr.write(`[coingecko] globalMarket btcDominance=${result.btcDominance}%
|
|
591
|
+
`);
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
async function fetchTopCoins(limit = 50) {
|
|
595
|
+
process.stderr.write(`[coingecko] fetchTopCoins limit=${limit}
|
|
596
|
+
`);
|
|
597
|
+
const res = await import_axios3.default.get(`${BASE}/coins/markets`, {
|
|
598
|
+
timeout: 8e3,
|
|
599
|
+
params: {
|
|
600
|
+
vs_currency: "usd",
|
|
601
|
+
order: "market_cap_desc",
|
|
602
|
+
per_page: limit,
|
|
603
|
+
page: 1,
|
|
604
|
+
sparkline: false
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
const coins = res.data.map((c, i) => ({
|
|
608
|
+
rank: i + 1,
|
|
609
|
+
id: c.id,
|
|
610
|
+
symbol: c.symbol.toUpperCase(),
|
|
611
|
+
name: c.name,
|
|
612
|
+
price: c.current_price,
|
|
613
|
+
changePercent24h: parseFloat((c.price_change_percentage_24h ?? 0).toFixed(2)),
|
|
614
|
+
marketCap: c.market_cap,
|
|
615
|
+
volume24h: c.total_volume
|
|
616
|
+
}));
|
|
617
|
+
process.stderr.write(`[coingecko] fetchTopCoins \u8FD4\u56DE ${coins.length} \u6761
|
|
618
|
+
`);
|
|
619
|
+
return coins;
|
|
620
|
+
}
|
|
621
|
+
async function fetchCategories() {
|
|
622
|
+
process.stderr.write(`[coingecko] fetchCategories
|
|
623
|
+
`);
|
|
624
|
+
const res = await import_axios3.default.get(`${BASE}/coins/categories`, { timeout: 8e3 });
|
|
625
|
+
const categories = res.data.map((c) => ({
|
|
626
|
+
id: c.id,
|
|
627
|
+
name: c.name,
|
|
628
|
+
marketCap: c.market_cap ?? 0,
|
|
629
|
+
marketCapChange24h: parseFloat((c.market_cap_change_24h ?? 0).toFixed(2)),
|
|
630
|
+
volume24h: c.volume_24h ?? 0,
|
|
631
|
+
topCoins: c.top_3_coins ?? []
|
|
632
|
+
}));
|
|
633
|
+
process.stderr.write(`[coingecko] fetchCategories \u8FD4\u56DE ${categories.length} \u6761
|
|
634
|
+
`);
|
|
635
|
+
return categories;
|
|
636
|
+
}
|
|
637
|
+
async function fetchFearGreed() {
|
|
638
|
+
process.stderr.write(`[coingecko] fetchFearGreed
|
|
639
|
+
`);
|
|
640
|
+
const res = await import_axios3.default.get(FEAR_GREED_URL, { timeout: 5e3 });
|
|
641
|
+
const d = res.data.data[0];
|
|
642
|
+
const result = {
|
|
643
|
+
value: parseInt(d.value),
|
|
644
|
+
label: d.value_classification,
|
|
645
|
+
timestamp: new Date(parseInt(d.timestamp) * 1e3).toISOString().slice(0, 10)
|
|
646
|
+
};
|
|
647
|
+
process.stderr.write(`[coingecko] fearGreed value=${result.value} label=${result.label}
|
|
648
|
+
`);
|
|
649
|
+
return result;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/tools/crypto.ts
|
|
653
|
+
var getCryptoOverviewTool = {
|
|
654
|
+
tool: {
|
|
655
|
+
name: "get_crypto_overview",
|
|
656
|
+
description: "\u83B7\u53D6\u52A0\u5BC6\u8D27\u5E01\u5E02\u573A\u5168\u5C40\u6982\u51B5\uFF1A\u603B\u5E02\u503C\u300124h\u6210\u4EA4\u91CF\u3001BTC/ETH\u5360\u6BD4\u3001\u5E02\u503C\u53D8\u5316\uFF0C\u4EE5\u53CA\u6050\u60E7\u8D2A\u5A6A\u6307\u6570\u3002",
|
|
657
|
+
inputSchema: {
|
|
658
|
+
type: "object",
|
|
659
|
+
properties: {},
|
|
660
|
+
required: []
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
handler: async () => {
|
|
664
|
+
try {
|
|
665
|
+
const [market, fg] = await Promise.all([fetchGlobalMarket(), fetchFearGreed()]);
|
|
666
|
+
return ok({ market, fearGreed: fg });
|
|
667
|
+
} catch (e) {
|
|
668
|
+
return err(e.message);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
var TopCoinsInput = import_zod5.z.object({
|
|
673
|
+
limit: import_zod5.z.number().int().min(1).max(100).optional()
|
|
674
|
+
});
|
|
675
|
+
var getCryptoTopTool = {
|
|
676
|
+
tool: {
|
|
677
|
+
name: "get_crypto_top",
|
|
678
|
+
description: "\u6309\u5E02\u503C\u6392\u540D\u83B7\u53D6 Top N \u52A0\u5BC6\u8D27\u5E01\uFF0C\u542B\u4EF7\u683C\u3001\u6DA8\u8DCC\u5E45\u3001\u5E02\u503C\u300124h\u6210\u4EA4\u91CF\u3002\u9ED8\u8BA4 Top 20\u3002",
|
|
679
|
+
inputSchema: {
|
|
680
|
+
type: "object",
|
|
681
|
+
properties: {
|
|
682
|
+
limit: {
|
|
683
|
+
type: "number",
|
|
684
|
+
description: "\u8FD4\u56DE\u6570\u91CF\uFF0C\u9ED8\u8BA420\uFF0C\u6700\u5927100"
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
required: []
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
handler: async (input) => {
|
|
691
|
+
const parsed = TopCoinsInput.safeParse(input);
|
|
692
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
693
|
+
const { limit = 20 } = parsed.data;
|
|
694
|
+
try {
|
|
695
|
+
return ok(await fetchTopCoins(limit));
|
|
696
|
+
} catch (e) {
|
|
697
|
+
return err(e.message);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
var getCryptoCatsTool = {
|
|
702
|
+
tool: {
|
|
703
|
+
name: "get_crypto_categories",
|
|
704
|
+
description: "\u83B7\u53D6\u52A0\u5BC6\u8D27\u5E01\u8D5B\u9053\u5206\u7C7B\u6570\u636E\uFF08DeFi\u3001Layer1\u3001AI\u3001GameFi\u7B49\uFF09\uFF0C\u542B\u5404\u8D5B\u9053\u5E02\u503C\u548C24h\u6DA8\u8DCC\u5E45\u3002",
|
|
705
|
+
inputSchema: {
|
|
706
|
+
type: "object",
|
|
707
|
+
properties: {},
|
|
708
|
+
required: []
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
handler: async () => {
|
|
712
|
+
try {
|
|
713
|
+
const cats = await fetchCategories();
|
|
714
|
+
const sorted = cats.sort((a, b) => (b.marketCap ?? 0) - (a.marketCap ?? 0));
|
|
715
|
+
return ok(sorted);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
return err(e.message);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
var FundingInput = import_zod5.z.object({
|
|
722
|
+
symbols: import_zod5.z.array(import_zod5.z.string()).min(1).max(20)
|
|
723
|
+
});
|
|
724
|
+
var getCryptoFundingTool = {
|
|
725
|
+
tool: {
|
|
726
|
+
name: "get_funding_rate",
|
|
727
|
+
description: "\u83B7\u53D6 Binance \u6C38\u7EED\u5408\u7EA6\u8D44\u91D1\u8D39\u7387\u3002symbols \u4F20\u5E01\u79CD\u4EE3\u7801\uFF0C\u5982 ['BTC','ETH']\uFF0C\u81EA\u52A8\u8865\u5168 USDT \u540E\u7F00\u3002",
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: "object",
|
|
730
|
+
properties: {
|
|
731
|
+
symbols: {
|
|
732
|
+
type: "array",
|
|
733
|
+
items: { type: "string" },
|
|
734
|
+
description: "\u5E01\u79CD\u4EE3\u7801\u5217\u8868\uFF0C\u5982 ['BTC','ETH','SOL']",
|
|
735
|
+
minItems: 1,
|
|
736
|
+
maxItems: 20
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
required: ["symbols"]
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
handler: async (input) => {
|
|
743
|
+
const parsed = FundingInput.safeParse(input);
|
|
744
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
745
|
+
const { symbols } = parsed.data;
|
|
746
|
+
try {
|
|
747
|
+
const results = await Promise.all(
|
|
748
|
+
symbols.map(async (raw) => {
|
|
749
|
+
const sym = raw.toUpperCase().endsWith("USDT") ? raw.toUpperCase() : raw.toUpperCase() + "USDT";
|
|
750
|
+
try {
|
|
751
|
+
const res = await import_axios4.default.get(
|
|
752
|
+
`https://fapi.binance.com/fapi/v1/premiumIndex?symbol=${sym}`,
|
|
753
|
+
{ timeout: 5e3 }
|
|
754
|
+
);
|
|
755
|
+
const d = res.data;
|
|
756
|
+
return {
|
|
757
|
+
symbol: sym,
|
|
758
|
+
markPrice: parseFloat(d.markPrice),
|
|
759
|
+
indexPrice: parseFloat(d.indexPrice),
|
|
760
|
+
fundingRate: parseFloat(d.lastFundingRate),
|
|
761
|
+
fundingRatePct: (parseFloat(d.lastFundingRate) * 100).toFixed(4) + "%",
|
|
762
|
+
nextFundingTime: new Date(d.nextFundingTime).toISOString()
|
|
763
|
+
};
|
|
764
|
+
} catch {
|
|
765
|
+
return { symbol: sym, error: "not found or not a perpetual contract" };
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
);
|
|
769
|
+
return ok(results);
|
|
770
|
+
} catch (e) {
|
|
771
|
+
return err(e.message);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/tools/index.ts
|
|
777
|
+
var tools = [
|
|
778
|
+
getQuoteTool,
|
|
779
|
+
getKlineTool,
|
|
780
|
+
searchStockTool,
|
|
781
|
+
getUsIndicesTool,
|
|
782
|
+
getStockProfileTool,
|
|
783
|
+
getCryptoOverviewTool,
|
|
784
|
+
getCryptoTopTool,
|
|
785
|
+
getCryptoCatsTool,
|
|
786
|
+
getCryptoFundingTool
|
|
787
|
+
];
|
|
788
|
+
var toolMap = new Map(
|
|
789
|
+
tools.map((t) => [t.tool.name, t])
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
// src/server.ts
|
|
793
|
+
var server = new import_server.Server(
|
|
794
|
+
{ name: "stock-mcp", version: "1.0.0" },
|
|
795
|
+
{ capabilities: { tools: {} } }
|
|
796
|
+
);
|
|
797
|
+
server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
|
|
798
|
+
tools: tools.map((t) => t.tool)
|
|
799
|
+
}));
|
|
800
|
+
server.setRequestHandler(import_types6.CallToolRequestSchema, async (request) => {
|
|
801
|
+
const { name, arguments: args } = request.params;
|
|
802
|
+
const def = toolMap.get(name);
|
|
803
|
+
if (!def) {
|
|
804
|
+
return {
|
|
805
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
806
|
+
isError: true
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
return def.handler(args ?? {});
|
|
810
|
+
});
|
|
811
|
+
async function startServer() {
|
|
812
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
813
|
+
await server.connect(transport);
|
|
814
|
+
process.stderr.write(`[stock-mcp] server started \u2014 ${tools.length} tools registered
|
|
815
|
+
`);
|
|
816
|
+
for (const t of tools) {
|
|
817
|
+
process.stderr.write(` \xB7 ${t.tool.name}
|
|
818
|
+
`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/index.ts
|
|
823
|
+
startServer().catch((e) => {
|
|
824
|
+
process.stderr.write(`[stock-mcp] fatal: ${e}
|
|
825
|
+
`);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
});
|