xstock-mcp 1.1.0 → 1.2.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/dist/index.js +275 -19
- package/package.json +1 -1
- package/src/data/yahoo.ts +182 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/us-fundamentals.ts +174 -0
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/server.ts
|
|
27
27
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
28
28
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
-
var
|
|
29
|
+
var import_types7 = require("@modelcontextprotocol/sdk/types.js");
|
|
30
30
|
|
|
31
31
|
// src/tools/quotes.ts
|
|
32
32
|
var import_zod = require("zod");
|
|
@@ -169,6 +169,7 @@ async function fetchHKKline(code, period = "daily") {
|
|
|
169
169
|
|
|
170
170
|
// src/data/yahoo.ts
|
|
171
171
|
var import_yahoo_finance2 = __toESM(require("yahoo-finance2"));
|
|
172
|
+
var import_axios2 = __toESM(require("axios"));
|
|
172
173
|
var yf = new import_yahoo_finance2.default({ suppressNotices: ["yahooSurvey"] });
|
|
173
174
|
async function fetchUSQuotes(symbols) {
|
|
174
175
|
process.stderr.write(`[yahoo] fetchUSQuotes symbols=${symbols.join(",")}
|
|
@@ -254,6 +255,119 @@ async function fetchEarningsCalendar(symbol) {
|
|
|
254
255
|
recentEPS
|
|
255
256
|
};
|
|
256
257
|
}
|
|
258
|
+
async function fetchFinancials(symbol, quarterly = false) {
|
|
259
|
+
process.stderr.write(`[yahoo] fetchFinancials symbol=${symbol} quarterly=${quarterly}
|
|
260
|
+
`);
|
|
261
|
+
const modules = quarterly ? ["incomeStatementHistoryQuarterly", "cashflowStatementHistoryQuarterly", "balanceSheetHistoryQuarterly"] : ["incomeStatementHistory", "cashflowStatementHistory", "balanceSheetHistory"];
|
|
262
|
+
const summary = await yf.quoteSummary(symbol, { modules });
|
|
263
|
+
const incomeKey = quarterly ? "incomeStatementHistoryQuarterly" : "incomeStatementHistory";
|
|
264
|
+
const cashKey = quarterly ? "cashflowStatementHistoryQuarterly" : "cashflowStatementHistory";
|
|
265
|
+
const balanceKey = quarterly ? "balanceSheetHistoryQuarterly" : "balanceSheetHistory";
|
|
266
|
+
const incomeArr = summary[incomeKey]?.incomeStatementHistory ?? [];
|
|
267
|
+
const cashArr = summary[cashKey]?.cashflowStatements ?? [];
|
|
268
|
+
const balanceArr = summary[balanceKey]?.balanceSheetStatements ?? [];
|
|
269
|
+
return incomeArr.slice(0, 4).map((inc, i) => {
|
|
270
|
+
const cash = cashArr[i] ?? {};
|
|
271
|
+
const bal = balanceArr[i] ?? {};
|
|
272
|
+
const date = inc.endDate instanceof Date ? inc.endDate.toISOString().slice(0, 10) : String(inc.endDate ?? "");
|
|
273
|
+
const revenue = inc.totalRevenue ?? null;
|
|
274
|
+
const gross = inc.grossProfit ?? null;
|
|
275
|
+
const net = inc.netIncome ?? null;
|
|
276
|
+
const opCash = cash.totalCashFromOperatingActivities ?? null;
|
|
277
|
+
const capex = cash.capitalExpenditures ?? null;
|
|
278
|
+
const debt = bal.totalDebt ?? (bal.longTermDebt ?? null);
|
|
279
|
+
const equity = bal.totalStockholderEquity ?? null;
|
|
280
|
+
return {
|
|
281
|
+
date,
|
|
282
|
+
totalRevenue: revenue,
|
|
283
|
+
grossProfit: gross,
|
|
284
|
+
grossMargin: revenue && gross ? Math.round(gross / revenue * 1e4) / 100 : null,
|
|
285
|
+
netIncome: net,
|
|
286
|
+
netMargin: revenue && net ? Math.round(net / revenue * 1e4) / 100 : null,
|
|
287
|
+
epsDiluted: inc.dilutedEPS ?? null,
|
|
288
|
+
operatingCashFlow: opCash,
|
|
289
|
+
freeCashFlow: opCash !== null && capex !== null ? opCash + capex : null,
|
|
290
|
+
totalDebt: debt,
|
|
291
|
+
stockholdersEquity: equity,
|
|
292
|
+
debtToEquity: debt !== null && equity !== null && equity !== 0 ? Math.round(debt / equity * 100) / 100 : null
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async function fetchAnalystRating(symbol) {
|
|
297
|
+
process.stderr.write(`[yahoo] fetchAnalystRating symbol=${symbol}
|
|
298
|
+
`);
|
|
299
|
+
const summary = await yf.quoteSummary(symbol, {
|
|
300
|
+
modules: ["financialData", "recommendationTrend", "upgradeDowngradeHistory"]
|
|
301
|
+
});
|
|
302
|
+
const fin = summary.financialData;
|
|
303
|
+
const trend = summary.recommendationTrend;
|
|
304
|
+
const history = summary.upgradeDowngradeHistory;
|
|
305
|
+
const trendArr = trend?.trend ?? [];
|
|
306
|
+
const latest = trendArr[0] ?? {};
|
|
307
|
+
const changes = (history?.history ?? []).slice(0, 10).map((h) => ({
|
|
308
|
+
date: h.epochGradeDate instanceof Date ? h.epochGradeDate.toISOString().slice(0, 10) : String(h.epochGradeDate ?? ""),
|
|
309
|
+
firm: String(h.firm ?? ""),
|
|
310
|
+
action: String(h.action ?? ""),
|
|
311
|
+
from: h.fromGrade ? String(h.fromGrade) : null,
|
|
312
|
+
to: h.toGrade ? String(h.toGrade) : null
|
|
313
|
+
}));
|
|
314
|
+
return {
|
|
315
|
+
symbol,
|
|
316
|
+
consensus: fin?.recommendationKey ?? null,
|
|
317
|
+
targetPriceMean: fin?.targetMeanPrice ?? null,
|
|
318
|
+
targetPriceHigh: fin?.targetHighPrice ?? null,
|
|
319
|
+
targetPriceLow: fin?.targetLowPrice ?? null,
|
|
320
|
+
distribution: {
|
|
321
|
+
strongBuy: Number(latest.strongBuy ?? 0),
|
|
322
|
+
buy: Number(latest.buy ?? 0),
|
|
323
|
+
hold: Number(latest.hold ?? 0),
|
|
324
|
+
sell: Number(latest.sell ?? 0),
|
|
325
|
+
strongSell: Number(latest.strongSell ?? 0)
|
|
326
|
+
},
|
|
327
|
+
recentChanges: changes
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function extractXmlTag(xml, tag) {
|
|
331
|
+
const match = xml.match(new RegExp(`<${tag}[^>]*>(?:<!\\[CDATA\\[)?([\\s\\S]*?)(?:\\]\\]>)?</${tag}>`, "i"));
|
|
332
|
+
return match ? match[1].trim() : "";
|
|
333
|
+
}
|
|
334
|
+
async function fetchStockNews(symbol, limit = 20) {
|
|
335
|
+
process.stderr.write(`[yahoo] fetchStockNews symbol=${symbol}
|
|
336
|
+
`);
|
|
337
|
+
const url = `https://feeds.finance.yahoo.com/rss/2.0/headline?s=${symbol}®ion=US&lang=en-US`;
|
|
338
|
+
const res = await import_axios2.default.get(url, { timeout: 8e3, responseType: "text" });
|
|
339
|
+
const xml = res.data;
|
|
340
|
+
const items = [];
|
|
341
|
+
const itemMatches = xml.match(/<item>[\s\S]*?<\/item>/g) ?? [];
|
|
342
|
+
for (const item of itemMatches.slice(0, limit)) {
|
|
343
|
+
items.push({
|
|
344
|
+
title: extractXmlTag(item, "title"),
|
|
345
|
+
link: extractXmlTag(item, "link") || extractXmlTag(item, "guid"),
|
|
346
|
+
summary: extractXmlTag(item, "description").replace(/<[^>]+>/g, "").slice(0, 200),
|
|
347
|
+
pubDate: extractXmlTag(item, "pubDate")
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
process.stderr.write(`[yahoo] fetchStockNews \u8FD4\u56DE ${items.length} \u6761
|
|
351
|
+
`);
|
|
352
|
+
return items;
|
|
353
|
+
}
|
|
354
|
+
async function fetchInsiderActivity(symbol) {
|
|
355
|
+
process.stderr.write(`[yahoo] fetchInsiderActivity symbol=${symbol}
|
|
356
|
+
`);
|
|
357
|
+
const summary = await yf.quoteSummary(symbol, { modules: ["insiderTransactions"] });
|
|
358
|
+
const raw = summary.insiderTransactions;
|
|
359
|
+
const txArr = raw?.transactions ?? [];
|
|
360
|
+
return txArr.slice(0, 20).map((tx) => ({
|
|
361
|
+
date: tx.startDate instanceof Date ? tx.startDate.toISOString().slice(0, 10) : String(tx.startDate ?? ""),
|
|
362
|
+
name: String(tx.filerName ?? ""),
|
|
363
|
+
relation: String(tx.filerRelation ?? ""),
|
|
364
|
+
transactionType: String(tx.transactionDescription ?? ""),
|
|
365
|
+
shares: tx.shares ?? null,
|
|
366
|
+
value: tx.value ?? null,
|
|
367
|
+
sharesBefore: tx.shareholderBefore ?? null,
|
|
368
|
+
sharesAfter: tx.shareholderAfter ?? null
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
257
371
|
async function fetchStockProfile(symbol) {
|
|
258
372
|
process.stderr.write(`[yahoo] fetchStockProfile symbol=${symbol}
|
|
259
373
|
`);
|
|
@@ -286,7 +400,7 @@ async function fetchStockProfile(symbol) {
|
|
|
286
400
|
}
|
|
287
401
|
|
|
288
402
|
// src/data/binance.ts
|
|
289
|
-
var
|
|
403
|
+
var import_axios3 = __toESM(require("axios"));
|
|
290
404
|
function normalizeSymbol(symbol) {
|
|
291
405
|
const s = symbol.toUpperCase();
|
|
292
406
|
return s.endsWith("USDT") ? s : s + "USDT";
|
|
@@ -298,7 +412,7 @@ async function fetchCryptoTickers(symbols) {
|
|
|
298
412
|
symbols.map(async (raw) => {
|
|
299
413
|
const sym = normalizeSymbol(raw);
|
|
300
414
|
try {
|
|
301
|
-
const res = await
|
|
415
|
+
const res = await import_axios3.default.get(
|
|
302
416
|
`https://api.binance.com/api/v3/ticker/24hr?symbol=${sym}`,
|
|
303
417
|
{ timeout: 5e3 }
|
|
304
418
|
);
|
|
@@ -331,11 +445,11 @@ async function fetchMarketSentiment(symbol, period = "1h") {
|
|
|
331
445
|
const base = "https://fapi.binance.com";
|
|
332
446
|
const params = `symbol=${sym}&period=${period}&limit=1`;
|
|
333
447
|
const [oiRes, globalRes, topAccRes, topPosRes, takerRes] = await Promise.all([
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
448
|
+
import_axios3.default.get(`${base}/fapi/v1/openInterest?symbol=${sym}`, { timeout: 5e3 }),
|
|
449
|
+
import_axios3.default.get(`${base}/futures/data/globalLongShortAccountRatio?${params}`, { timeout: 5e3 }),
|
|
450
|
+
import_axios3.default.get(`${base}/futures/data/topLongShortAccountRatio?${params}`, { timeout: 5e3 }),
|
|
451
|
+
import_axios3.default.get(`${base}/futures/data/topLongShortPositionRatio?${params}`, { timeout: 5e3 }),
|
|
452
|
+
import_axios3.default.get(`${base}/futures/data/takerlongshortRatio?${params}`, { timeout: 5e3 })
|
|
339
453
|
]);
|
|
340
454
|
const oi = oiRes.data;
|
|
341
455
|
const global = globalRes.data[0] ?? {};
|
|
@@ -360,7 +474,7 @@ async function fetchCryptoKlines(symbol, interval = "1d", limit = 100) {
|
|
|
360
474
|
const sym = normalizeSymbol(symbol);
|
|
361
475
|
process.stderr.write(`[binance] fetchCryptoKlines symbol=${sym} interval=${interval} limit=${limit}
|
|
362
476
|
`);
|
|
363
|
-
const res = await
|
|
477
|
+
const res = await import_axios3.default.get(
|
|
364
478
|
`https://api.binance.com/api/v3/klines?symbol=${sym}&interval=${interval}&limit=${limit}`,
|
|
365
479
|
{ timeout: 5e3 }
|
|
366
480
|
);
|
|
@@ -909,16 +1023,16 @@ var getStockProfileTool = {
|
|
|
909
1023
|
|
|
910
1024
|
// src/tools/crypto.ts
|
|
911
1025
|
var import_zod5 = require("zod");
|
|
912
|
-
var
|
|
1026
|
+
var import_axios5 = __toESM(require("axios"));
|
|
913
1027
|
|
|
914
1028
|
// src/data/coingecko.ts
|
|
915
|
-
var
|
|
1029
|
+
var import_axios4 = __toESM(require("axios"));
|
|
916
1030
|
var BASE = "https://api.coingecko.com/api/v3";
|
|
917
1031
|
var FEAR_GREED_URL = "https://api.alternative.me/fng/?limit=1";
|
|
918
1032
|
async function fetchGlobalMarket() {
|
|
919
1033
|
process.stderr.write(`[coingecko] fetchGlobalMarket
|
|
920
1034
|
`);
|
|
921
|
-
const res = await
|
|
1035
|
+
const res = await import_axios4.default.get(`${BASE}/global`, { timeout: 8e3 });
|
|
922
1036
|
const d = res.data.data;
|
|
923
1037
|
const result = {
|
|
924
1038
|
totalMarketCapUsd: d.total_market_cap?.usd ?? 0,
|
|
@@ -935,7 +1049,7 @@ async function fetchGlobalMarket() {
|
|
|
935
1049
|
async function fetchTopCoins(limit = 50) {
|
|
936
1050
|
process.stderr.write(`[coingecko] fetchTopCoins limit=${limit}
|
|
937
1051
|
`);
|
|
938
|
-
const res = await
|
|
1052
|
+
const res = await import_axios4.default.get(`${BASE}/coins/markets`, {
|
|
939
1053
|
timeout: 8e3,
|
|
940
1054
|
params: {
|
|
941
1055
|
vs_currency: "usd",
|
|
@@ -962,7 +1076,7 @@ async function fetchTopCoins(limit = 50) {
|
|
|
962
1076
|
async function fetchCategories() {
|
|
963
1077
|
process.stderr.write(`[coingecko] fetchCategories
|
|
964
1078
|
`);
|
|
965
|
-
const res = await
|
|
1079
|
+
const res = await import_axios4.default.get(`${BASE}/coins/categories`, { timeout: 8e3 });
|
|
966
1080
|
const categories = res.data.map((c) => ({
|
|
967
1081
|
id: c.id,
|
|
968
1082
|
name: c.name,
|
|
@@ -978,7 +1092,7 @@ async function fetchCategories() {
|
|
|
978
1092
|
async function fetchFearGreed() {
|
|
979
1093
|
process.stderr.write(`[coingecko] fetchFearGreed
|
|
980
1094
|
`);
|
|
981
|
-
const res = await
|
|
1095
|
+
const res = await import_axios4.default.get(FEAR_GREED_URL, { timeout: 5e3 });
|
|
982
1096
|
const d = res.data.data[0];
|
|
983
1097
|
const result = {
|
|
984
1098
|
value: parseInt(d.value),
|
|
@@ -1089,7 +1203,7 @@ var getCryptoFundingTool = {
|
|
|
1089
1203
|
symbols.map(async (raw) => {
|
|
1090
1204
|
const sym = raw.toUpperCase().endsWith("USDT") ? raw.toUpperCase() : raw.toUpperCase() + "USDT";
|
|
1091
1205
|
try {
|
|
1092
|
-
const res = await
|
|
1206
|
+
const res = await import_axios5.default.get(
|
|
1093
1207
|
`https://fapi.binance.com/fapi/v1/premiumIndex?symbol=${sym}`,
|
|
1094
1208
|
{ timeout: 5e3 }
|
|
1095
1209
|
);
|
|
@@ -1161,6 +1275,143 @@ var getCryptoLiquidationTool = {
|
|
|
1161
1275
|
}
|
|
1162
1276
|
};
|
|
1163
1277
|
|
|
1278
|
+
// src/tools/us-fundamentals.ts
|
|
1279
|
+
var import_zod6 = require("zod");
|
|
1280
|
+
var SymbolInput2 = import_zod6.z.object({ symbol: import_zod6.z.string().min(1) });
|
|
1281
|
+
var FinancialsInput = import_zod6.z.object({
|
|
1282
|
+
symbol: import_zod6.z.string().min(1),
|
|
1283
|
+
quarterly: import_zod6.z.boolean().optional()
|
|
1284
|
+
});
|
|
1285
|
+
var getFinancialsTool = {
|
|
1286
|
+
tool: {
|
|
1287
|
+
name: "get_financials",
|
|
1288
|
+
description: "\u83B7\u53D6\u7F8E\u80A1\u8D22\u52A1\u6570\u636E\uFF1A\u8425\u6536\u3001\u6BDB\u5229\u7387\u3001\u51C0\u5229\u6DA6\u3001\u51C0\u5229\u7387\u3001EPS\u3001\u81EA\u7531\u73B0\u91D1\u6D41\u3001\u8D1F\u503A\u7387\u3002\u9ED8\u8BA4\u8FD4\u56DE\u8FD14\u5E74\u5E74\u62A5\uFF0Cquarterly=true \u8FD4\u56DE\u8FD14\u5B63\u5EA6\u5B63\u62A5\u3002",
|
|
1289
|
+
inputSchema: {
|
|
1290
|
+
type: "object",
|
|
1291
|
+
properties: {
|
|
1292
|
+
symbol: { type: "string", description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001NVDA" },
|
|
1293
|
+
quarterly: { type: "boolean", description: "true=\u5B63\u62A5\uFF0Cfalse=\u5E74\u62A5\uFF08\u9ED8\u8BA4\uFF09" }
|
|
1294
|
+
},
|
|
1295
|
+
required: ["symbol"]
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
handler: async (input) => {
|
|
1299
|
+
const parsed = FinancialsInput.safeParse(input);
|
|
1300
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
1301
|
+
try {
|
|
1302
|
+
return ok(await fetchFinancials(parsed.data.symbol, parsed.data.quarterly ?? false));
|
|
1303
|
+
} catch (e) {
|
|
1304
|
+
return err(e.message);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
var getAnalystRatingTool = {
|
|
1309
|
+
tool: {
|
|
1310
|
+
name: "get_analyst_rating",
|
|
1311
|
+
description: "\u83B7\u53D6\u534E\u5C14\u8857\u5206\u6790\u5E08\u8BC4\u7EA7\uFF1A\u5171\u8BC6\u8BC4\u7EA7\uFF08buy/hold/sell\uFF09\u3001\u5E73\u5747\u76EE\u6807\u4EF7\u3001\u9AD8\u4F4E\u76EE\u6807\u4EF7\u3001\u5F3A\u4E70/\u4E70/\u6301\u6709/\u5356/\u5F3A\u5356\u4EBA\u6570\u5206\u5E03\uFF0C\u4EE5\u53CA\u6700\u8FD110\u6761\u8BC4\u7EA7\u53D8\u52A8\u8BB0\u5F55\u3002",
|
|
1312
|
+
inputSchema: {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
properties: {
|
|
1315
|
+
symbol: { type: "string", description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001NVDA" }
|
|
1316
|
+
},
|
|
1317
|
+
required: ["symbol"]
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
handler: async (input) => {
|
|
1321
|
+
const parsed = SymbolInput2.safeParse(input);
|
|
1322
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
1323
|
+
try {
|
|
1324
|
+
return ok(await fetchAnalystRating(parsed.data.symbol));
|
|
1325
|
+
} catch (e) {
|
|
1326
|
+
return err(e.message);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
var NewsInput = import_zod6.z.object({
|
|
1331
|
+
symbol: import_zod6.z.string().min(1),
|
|
1332
|
+
limit: import_zod6.z.number().int().min(1).max(30).optional()
|
|
1333
|
+
});
|
|
1334
|
+
var getStockNewsTool = {
|
|
1335
|
+
tool: {
|
|
1336
|
+
name: "get_stock_news",
|
|
1337
|
+
description: "\u83B7\u53D6\u7F8E\u80A1\u4E2A\u80A1\u6700\u65B0\u65B0\u95FB\uFF08\u8FD17\u5929\uFF09\uFF0C\u542B\u6807\u9898\u3001\u6458\u8981\u3001\u94FE\u63A5\u3001\u53D1\u5E03\u65F6\u95F4\u3002\u6700\u591A30\u6761\uFF0C\u9ED8\u8BA415\u6761\u3002",
|
|
1338
|
+
inputSchema: {
|
|
1339
|
+
type: "object",
|
|
1340
|
+
properties: {
|
|
1341
|
+
symbol: { type: "string", description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001NVDA" },
|
|
1342
|
+
limit: { type: "number", description: "\u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA415\uFF0C\u6700\u592730" }
|
|
1343
|
+
},
|
|
1344
|
+
required: ["symbol"]
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
handler: async (input) => {
|
|
1348
|
+
const parsed = NewsInput.safeParse(input);
|
|
1349
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
1350
|
+
try {
|
|
1351
|
+
return ok(await fetchStockNews(parsed.data.symbol, parsed.data.limit ?? 15));
|
|
1352
|
+
} catch (e) {
|
|
1353
|
+
return err(e.message);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
var getInsiderActivityTool = {
|
|
1358
|
+
tool: {
|
|
1359
|
+
name: "get_insider_activity",
|
|
1360
|
+
description: "\u83B7\u53D6\u7F8E\u80A1\u5185\u90E8\u4EBA\uFF08\u9AD8\u7BA1/\u8463\u4E8B\uFF09\u8FD1\u671F\u4E70\u5356\u8BB0\u5F55\uFF0C\u57FA\u4E8E SEC Form 4 \u516C\u5F00\u6570\u636E\u3002\u542B\u4EA4\u6613\u4EBA\u59D3\u540D\u3001\u804C\u4F4D\u3001\u4EA4\u6613\u7C7B\u578B\u3001\u80A1\u6570\u3001\u4EA4\u6613\u91D1\u989D\u3002",
|
|
1361
|
+
inputSchema: {
|
|
1362
|
+
type: "object",
|
|
1363
|
+
properties: {
|
|
1364
|
+
symbol: { type: "string", description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001NVDA" }
|
|
1365
|
+
},
|
|
1366
|
+
required: ["symbol"]
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
handler: async (input) => {
|
|
1370
|
+
const parsed = SymbolInput2.safeParse(input);
|
|
1371
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
1372
|
+
try {
|
|
1373
|
+
return ok(await fetchInsiderActivity(parsed.data.symbol));
|
|
1374
|
+
} catch (e) {
|
|
1375
|
+
return err(e.message);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
var getStockFullOverviewTool = {
|
|
1380
|
+
tool: {
|
|
1381
|
+
name: "get_stock_full_overview",
|
|
1382
|
+
description: "\u4E00\u6B21\u8C03\u7528\u83B7\u53D6\u7F8E\u80A1\u5B8C\u6574\u5206\u6790\u6570\u636E\uFF1A\u5B9E\u65F6\u884C\u60C5 + \u516C\u53F8\u57FA\u672C\u9762 + \u5206\u6790\u5E08\u8BC4\u7EA7 + \u8FD1\u671F\u65B0\u95FB\uFF08\u5E76\u884C\u83B7\u53D6\uFF09\u3002\u9002\u5408\u56DE\u7B54\u7EFC\u5408\u5206\u6790\u7C7B\u95EE\u9898\uFF08\u5982\uFF1A\u5E2E\u6211\u5206\u6790\u4E00\u4E0B NVDA\uFF09\uFF0C\u907F\u514D\u591A\u6B21\u8C03\u7528\u4E0D\u540C\u5DE5\u5177\u3002",
|
|
1383
|
+
inputSchema: {
|
|
1384
|
+
type: "object",
|
|
1385
|
+
properties: {
|
|
1386
|
+
symbol: { type: "string", description: "\u7F8E\u80A1\u4EE3\u7801\uFF0C\u5982 AAPL\u3001NVDA\u3001TSLA" }
|
|
1387
|
+
},
|
|
1388
|
+
required: ["symbol"]
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
handler: async (input) => {
|
|
1392
|
+
const parsed = SymbolInput2.safeParse(input);
|
|
1393
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
1394
|
+
const { symbol } = parsed.data;
|
|
1395
|
+
try {
|
|
1396
|
+
const [quotes, profile, analyst, news] = await Promise.allSettled([
|
|
1397
|
+
fetchUSQuotes([symbol]),
|
|
1398
|
+
fetchStockProfile(symbol),
|
|
1399
|
+
fetchAnalystRating(symbol),
|
|
1400
|
+
fetchStockNews(symbol, 10)
|
|
1401
|
+
]);
|
|
1402
|
+
return ok({
|
|
1403
|
+
symbol,
|
|
1404
|
+
quote: quotes.status === "fulfilled" ? quotes.value[0] ?? null : null,
|
|
1405
|
+
profile: profile.status === "fulfilled" ? profile.value : null,
|
|
1406
|
+
analyst: analyst.status === "fulfilled" ? analyst.value : null,
|
|
1407
|
+
news: news.status === "fulfilled" ? news.value : []
|
|
1408
|
+
});
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
return err(e.message);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1164
1415
|
// src/tools/index.ts
|
|
1165
1416
|
var tools = [
|
|
1166
1417
|
getQuoteTool,
|
|
@@ -1175,7 +1426,12 @@ var tools = [
|
|
|
1175
1426
|
getCryptoTopTool,
|
|
1176
1427
|
getCryptoCatsTool,
|
|
1177
1428
|
getCryptoFundingTool,
|
|
1178
|
-
getCryptoLiquidationTool
|
|
1429
|
+
getCryptoLiquidationTool,
|
|
1430
|
+
getFinancialsTool,
|
|
1431
|
+
getAnalystRatingTool,
|
|
1432
|
+
getStockNewsTool,
|
|
1433
|
+
getInsiderActivityTool,
|
|
1434
|
+
getStockFullOverviewTool
|
|
1179
1435
|
];
|
|
1180
1436
|
var toolMap = new Map(
|
|
1181
1437
|
tools.map((t) => [t.tool.name, t])
|
|
@@ -1186,10 +1442,10 @@ var server = new import_server.Server(
|
|
|
1186
1442
|
{ name: "stock-mcp", version: "1.0.0" },
|
|
1187
1443
|
{ capabilities: { tools: {} } }
|
|
1188
1444
|
);
|
|
1189
|
-
server.setRequestHandler(
|
|
1445
|
+
server.setRequestHandler(import_types7.ListToolsRequestSchema, async () => ({
|
|
1190
1446
|
tools: tools.map((t) => t.tool)
|
|
1191
1447
|
}));
|
|
1192
|
-
server.setRequestHandler(
|
|
1448
|
+
server.setRequestHandler(import_types7.CallToolRequestSchema, async (request) => {
|
|
1193
1449
|
const { name, arguments: args } = request.params;
|
|
1194
1450
|
const def = toolMap.get(name);
|
|
1195
1451
|
if (!def) {
|
package/package.json
CHANGED
package/src/data/yahoo.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { default as YahooFinance } from "yahoo-finance2";
|
|
2
|
+
import axios from "axios";
|
|
2
3
|
|
|
3
4
|
// v3 需要实例化
|
|
4
5
|
const yf = new YahooFinance({ suppressNotices: ["yahooSurvey"] });
|
|
@@ -158,6 +159,187 @@ export async function fetchEarningsCalendar(symbol: string): Promise<EarningsCal
|
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
// ─── Financials ───────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
export interface FinancialPeriod {
|
|
165
|
+
date: string;
|
|
166
|
+
totalRevenue: number | null;
|
|
167
|
+
grossProfit: number | null;
|
|
168
|
+
grossMargin: number | null;
|
|
169
|
+
netIncome: number | null;
|
|
170
|
+
netMargin: number | null;
|
|
171
|
+
epsDiluted: number | null;
|
|
172
|
+
operatingCashFlow: number | null;
|
|
173
|
+
freeCashFlow: number | null;
|
|
174
|
+
totalDebt: number | null;
|
|
175
|
+
stockholdersEquity: number | null;
|
|
176
|
+
debtToEquity: number | null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function fetchFinancials(symbol: string, quarterly = false): Promise<FinancialPeriod[]> {
|
|
180
|
+
process.stderr.write(`[yahoo] fetchFinancials symbol=${symbol} quarterly=${quarterly}\n`);
|
|
181
|
+
const modules = quarterly
|
|
182
|
+
? ["incomeStatementHistoryQuarterly", "cashflowStatementHistoryQuarterly", "balanceSheetHistoryQuarterly"]
|
|
183
|
+
: ["incomeStatementHistory", "cashflowStatementHistory", "balanceSheetHistory"];
|
|
184
|
+
|
|
185
|
+
const summary = await yf.quoteSummary(symbol, { modules: modules as never[] });
|
|
186
|
+
|
|
187
|
+
const incomeKey = quarterly ? "incomeStatementHistoryQuarterly" : "incomeStatementHistory";
|
|
188
|
+
const cashKey = quarterly ? "cashflowStatementHistoryQuarterly" : "cashflowStatementHistory";
|
|
189
|
+
const balanceKey = quarterly ? "balanceSheetHistoryQuarterly" : "balanceSheetHistory";
|
|
190
|
+
|
|
191
|
+
const incomeArr = ((summary[incomeKey as keyof typeof summary] as Record<string, unknown>)
|
|
192
|
+
?.incomeStatementHistory as Record<string, unknown>[]) ?? [];
|
|
193
|
+
const cashArr = ((summary[cashKey as keyof typeof summary] as Record<string, unknown>)
|
|
194
|
+
?.cashflowStatements as Record<string, unknown>[]) ?? [];
|
|
195
|
+
const balanceArr = ((summary[balanceKey as keyof typeof summary] as Record<string, unknown>)
|
|
196
|
+
?.balanceSheetStatements as Record<string, unknown>[]) ?? [];
|
|
197
|
+
|
|
198
|
+
return incomeArr.slice(0, 4).map((inc, i) => {
|
|
199
|
+
const cash = cashArr[i] ?? {};
|
|
200
|
+
const bal = balanceArr[i] ?? {};
|
|
201
|
+
const date = inc.endDate instanceof Date ? inc.endDate.toISOString().slice(0, 10) : String(inc.endDate ?? "");
|
|
202
|
+
const revenue = inc.totalRevenue as number | null ?? null;
|
|
203
|
+
const gross = inc.grossProfit as number | null ?? null;
|
|
204
|
+
const net = inc.netIncome as number | null ?? null;
|
|
205
|
+
const opCash = cash.totalCashFromOperatingActivities as number | null ?? null;
|
|
206
|
+
const capex = cash.capitalExpenditures as number | null ?? null;
|
|
207
|
+
const debt = bal.totalDebt as number | null ?? (bal.longTermDebt as number | null ?? null);
|
|
208
|
+
const equity = bal.totalStockholderEquity as number | null ?? null;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
date,
|
|
212
|
+
totalRevenue: revenue,
|
|
213
|
+
grossProfit: gross,
|
|
214
|
+
grossMargin: revenue && gross ? Math.round((gross / revenue) * 10000) / 100 : null,
|
|
215
|
+
netIncome: net,
|
|
216
|
+
netMargin: revenue && net ? Math.round((net / revenue) * 10000) / 100 : null,
|
|
217
|
+
epsDiluted: inc.dilutedEPS as number | null ?? null,
|
|
218
|
+
operatingCashFlow: opCash,
|
|
219
|
+
freeCashFlow: opCash !== null && capex !== null ? opCash + capex : null,
|
|
220
|
+
totalDebt: debt,
|
|
221
|
+
stockholdersEquity: equity,
|
|
222
|
+
debtToEquity: debt !== null && equity !== null && equity !== 0
|
|
223
|
+
? Math.round((debt / equity) * 100) / 100 : null,
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Analyst Rating ───────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
export interface AnalystRating {
|
|
231
|
+
symbol: string;
|
|
232
|
+
consensus: string | null;
|
|
233
|
+
targetPriceMean: number | null;
|
|
234
|
+
targetPriceHigh: number | null;
|
|
235
|
+
targetPriceLow: number | null;
|
|
236
|
+
distribution: { strongBuy: number; buy: number; hold: number; sell: number; strongSell: number };
|
|
237
|
+
recentChanges: { date: string; firm: string; action: string; from: string | null; to: string | null }[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function fetchAnalystRating(symbol: string): Promise<AnalystRating> {
|
|
241
|
+
process.stderr.write(`[yahoo] fetchAnalystRating symbol=${symbol}\n`);
|
|
242
|
+
const summary = await yf.quoteSummary(symbol, {
|
|
243
|
+
modules: ["financialData", "recommendationTrend", "upgradeDowngradeHistory"],
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const fin = summary.financialData as Record<string, unknown> | undefined;
|
|
247
|
+
const trend = summary.recommendationTrend as Record<string, unknown> | undefined;
|
|
248
|
+
const history = summary.upgradeDowngradeHistory as Record<string, unknown> | undefined;
|
|
249
|
+
|
|
250
|
+
const trendArr = (trend?.trend as Record<string, unknown>[]) ?? [];
|
|
251
|
+
const latest = trendArr[0] ?? {};
|
|
252
|
+
|
|
253
|
+
const changes = ((history?.history as Record<string, unknown>[]) ?? []).slice(0, 10).map((h) => ({
|
|
254
|
+
date: h.epochGradeDate instanceof Date ? h.epochGradeDate.toISOString().slice(0, 10) : String(h.epochGradeDate ?? ""),
|
|
255
|
+
firm: String(h.firm ?? ""),
|
|
256
|
+
action: String(h.action ?? ""),
|
|
257
|
+
from: h.fromGrade ? String(h.fromGrade) : null,
|
|
258
|
+
to: h.toGrade ? String(h.toGrade) : null,
|
|
259
|
+
}));
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
symbol,
|
|
263
|
+
consensus: fin?.recommendationKey as string | null ?? null,
|
|
264
|
+
targetPriceMean: fin?.targetMeanPrice as number | null ?? null,
|
|
265
|
+
targetPriceHigh: fin?.targetHighPrice as number | null ?? null,
|
|
266
|
+
targetPriceLow: fin?.targetLowPrice as number | null ?? null,
|
|
267
|
+
distribution: {
|
|
268
|
+
strongBuy: Number(latest.strongBuy ?? 0),
|
|
269
|
+
buy: Number(latest.buy ?? 0),
|
|
270
|
+
hold: Number(latest.hold ?? 0),
|
|
271
|
+
sell: Number(latest.sell ?? 0),
|
|
272
|
+
strongSell: Number(latest.strongSell ?? 0),
|
|
273
|
+
},
|
|
274
|
+
recentChanges: changes,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── News ─────────────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
export interface StockNewsItem {
|
|
281
|
+
title: string;
|
|
282
|
+
link: string;
|
|
283
|
+
summary: string;
|
|
284
|
+
pubDate: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function extractXmlTag(xml: string, tag: string): string {
|
|
288
|
+
const match = xml.match(new RegExp(`<${tag}[^>]*>(?:<!\\[CDATA\\[)?([\\s\\S]*?)(?:\\]\\]>)?</${tag}>`, "i"));
|
|
289
|
+
return match ? match[1].trim() : "";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function fetchStockNews(symbol: string, limit = 20): Promise<StockNewsItem[]> {
|
|
293
|
+
process.stderr.write(`[yahoo] fetchStockNews symbol=${symbol}\n`);
|
|
294
|
+
const url = `https://feeds.finance.yahoo.com/rss/2.0/headline?s=${symbol}®ion=US&lang=en-US`;
|
|
295
|
+
const res = await axios.get<string>(url, { timeout: 8000, responseType: "text" });
|
|
296
|
+
const xml = res.data;
|
|
297
|
+
|
|
298
|
+
const items: StockNewsItem[] = [];
|
|
299
|
+
const itemMatches = xml.match(/<item>[\s\S]*?<\/item>/g) ?? [];
|
|
300
|
+
for (const item of itemMatches.slice(0, limit)) {
|
|
301
|
+
items.push({
|
|
302
|
+
title: extractXmlTag(item, "title"),
|
|
303
|
+
link: extractXmlTag(item, "link") || extractXmlTag(item, "guid"),
|
|
304
|
+
summary: extractXmlTag(item, "description").replace(/<[^>]+>/g, "").slice(0, 200),
|
|
305
|
+
pubDate: extractXmlTag(item, "pubDate"),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
process.stderr.write(`[yahoo] fetchStockNews 返回 ${items.length} 条\n`);
|
|
309
|
+
return items;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ─── Insider Activity ─────────────────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
export interface InsiderTransaction {
|
|
315
|
+
date: string;
|
|
316
|
+
name: string;
|
|
317
|
+
relation: string;
|
|
318
|
+
transactionType: string;
|
|
319
|
+
shares: number | null;
|
|
320
|
+
value: number | null;
|
|
321
|
+
sharesBefore: number | null;
|
|
322
|
+
sharesAfter: number | null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function fetchInsiderActivity(symbol: string): Promise<InsiderTransaction[]> {
|
|
326
|
+
process.stderr.write(`[yahoo] fetchInsiderActivity symbol=${symbol}\n`);
|
|
327
|
+
const summary = await yf.quoteSummary(symbol, { modules: ["insiderTransactions"] });
|
|
328
|
+
const raw = summary.insiderTransactions as Record<string, unknown> | undefined;
|
|
329
|
+
const txArr = (raw?.transactions as Record<string, unknown>[]) ?? [];
|
|
330
|
+
|
|
331
|
+
return txArr.slice(0, 20).map((tx) => ({
|
|
332
|
+
date: tx.startDate instanceof Date ? tx.startDate.toISOString().slice(0, 10) : String(tx.startDate ?? ""),
|
|
333
|
+
name: String(tx.filerName ?? ""),
|
|
334
|
+
relation: String(tx.filerRelation ?? ""),
|
|
335
|
+
transactionType: String(tx.transactionDescription ?? ""),
|
|
336
|
+
shares: tx.shares as number | null ?? null,
|
|
337
|
+
value: tx.value as number | null ?? null,
|
|
338
|
+
sharesBefore: tx.shareholderBefore as number | null ?? null,
|
|
339
|
+
sharesAfter: tx.shareholderAfter as number | null ?? null,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
161
343
|
export async function fetchStockProfile(symbol: string): Promise<StockProfile> {
|
|
162
344
|
process.stderr.write(`[yahoo] fetchStockProfile symbol=${symbol}\n`);
|
|
163
345
|
const summary = await yf.quoteSummary(symbol, {
|
package/src/tools/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { getKlineTool, getKlineWithIndicatorsTool } from "./kline";
|
|
|
4
4
|
import { searchStockTool } from "./search";
|
|
5
5
|
import { getUsIndicesTool, getStockProfileTool, getEarningsCalendarTool, getUsSectorHeatmapTool } from "./us-market";
|
|
6
6
|
import { getCryptoOverviewTool, getCryptoTopTool, getCryptoCatsTool, getCryptoFundingTool, getCryptoLiquidationTool } from "./crypto";
|
|
7
|
+
import { getFinancialsTool, getAnalystRatingTool, getStockNewsTool, getInsiderActivityTool, getStockFullOverviewTool } from "./us-fundamentals";
|
|
7
8
|
|
|
8
9
|
export * from "./types";
|
|
9
10
|
|
|
@@ -21,6 +22,11 @@ export const tools: ToolDef[] = [
|
|
|
21
22
|
getCryptoCatsTool,
|
|
22
23
|
getCryptoFundingTool,
|
|
23
24
|
getCryptoLiquidationTool,
|
|
25
|
+
getFinancialsTool,
|
|
26
|
+
getAnalystRatingTool,
|
|
27
|
+
getStockNewsTool,
|
|
28
|
+
getInsiderActivityTool,
|
|
29
|
+
getStockFullOverviewTool,
|
|
24
30
|
];
|
|
25
31
|
|
|
26
32
|
export const toolMap = new Map<string, ToolDef>(
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
fetchFinancials,
|
|
4
|
+
fetchAnalystRating,
|
|
5
|
+
fetchStockNews,
|
|
6
|
+
fetchInsiderActivity,
|
|
7
|
+
fetchUSQuotes,
|
|
8
|
+
fetchStockProfile,
|
|
9
|
+
} from "../data/yahoo";
|
|
10
|
+
import type { ToolDef } from "./types";
|
|
11
|
+
import { ok, err } from "./types";
|
|
12
|
+
|
|
13
|
+
const SymbolInput = z.object({ symbol: z.string().min(1) });
|
|
14
|
+
|
|
15
|
+
// ─── get_financials ───────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const FinancialsInput = z.object({
|
|
18
|
+
symbol: z.string().min(1),
|
|
19
|
+
quarterly: z.boolean().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const getFinancialsTool: ToolDef = {
|
|
23
|
+
tool: {
|
|
24
|
+
name: "get_financials",
|
|
25
|
+
description:
|
|
26
|
+
"获取美股财务数据:营收、毛利率、净利润、净利率、EPS、自由现金流、负债率。" +
|
|
27
|
+
"默认返回近4年年报,quarterly=true 返回近4季度季报。",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
symbol: { type: "string", description: "美股代码,如 AAPL、NVDA" },
|
|
32
|
+
quarterly: { type: "boolean", description: "true=季报,false=年报(默认)" },
|
|
33
|
+
},
|
|
34
|
+
required: ["symbol"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
handler: async (input) => {
|
|
38
|
+
const parsed = FinancialsInput.safeParse(input);
|
|
39
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
40
|
+
try {
|
|
41
|
+
return ok(await fetchFinancials(parsed.data.symbol, parsed.data.quarterly ?? false));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return err((e as Error).message);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ─── get_analyst_rating ───────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export const getAnalystRatingTool: ToolDef = {
|
|
51
|
+
tool: {
|
|
52
|
+
name: "get_analyst_rating",
|
|
53
|
+
description:
|
|
54
|
+
"获取华尔街分析师评级:共识评级(buy/hold/sell)、平均目标价、高低目标价、" +
|
|
55
|
+
"强买/买/持有/卖/强卖人数分布,以及最近10条评级变动记录。",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
symbol: { type: "string", description: "美股代码,如 AAPL、NVDA" },
|
|
60
|
+
},
|
|
61
|
+
required: ["symbol"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
handler: async (input) => {
|
|
65
|
+
const parsed = SymbolInput.safeParse(input);
|
|
66
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
67
|
+
try {
|
|
68
|
+
return ok(await fetchAnalystRating(parsed.data.symbol));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return err((e as Error).message);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ─── get_stock_news ───────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
const NewsInput = z.object({
|
|
78
|
+
symbol: z.string().min(1),
|
|
79
|
+
limit: z.number().int().min(1).max(30).optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const getStockNewsTool: ToolDef = {
|
|
83
|
+
tool: {
|
|
84
|
+
name: "get_stock_news",
|
|
85
|
+
description:
|
|
86
|
+
"获取美股个股最新新闻(近7天),含标题、摘要、链接、发布时间。最多30条,默认15条。",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
symbol: { type: "string", description: "美股代码,如 AAPL、NVDA" },
|
|
91
|
+
limit: { type: "number", description: "返回条数,默认15,最大30" },
|
|
92
|
+
},
|
|
93
|
+
required: ["symbol"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
handler: async (input) => {
|
|
97
|
+
const parsed = NewsInput.safeParse(input);
|
|
98
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
99
|
+
try {
|
|
100
|
+
return ok(await fetchStockNews(parsed.data.symbol, parsed.data.limit ?? 15));
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return err((e as Error).message);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ─── get_insider_activity ─────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export const getInsiderActivityTool: ToolDef = {
|
|
110
|
+
tool: {
|
|
111
|
+
name: "get_insider_activity",
|
|
112
|
+
description:
|
|
113
|
+
"获取美股内部人(高管/董事)近期买卖记录,基于 SEC Form 4 公开数据。" +
|
|
114
|
+
"含交易人姓名、职位、交易类型、股数、交易金额。",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
symbol: { type: "string", description: "美股代码,如 AAPL、NVDA" },
|
|
119
|
+
},
|
|
120
|
+
required: ["symbol"],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
handler: async (input) => {
|
|
124
|
+
const parsed = SymbolInput.safeParse(input);
|
|
125
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
126
|
+
try {
|
|
127
|
+
return ok(await fetchInsiderActivity(parsed.data.symbol));
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return err((e as Error).message);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ─── get_stock_full_overview ──────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
export const getStockFullOverviewTool: ToolDef = {
|
|
137
|
+
tool: {
|
|
138
|
+
name: "get_stock_full_overview",
|
|
139
|
+
description:
|
|
140
|
+
"一次调用获取美股完整分析数据:实时行情 + 公司基本面 + 分析师评级 + 近期新闻(并行获取)。" +
|
|
141
|
+
"适合回答综合分析类问题(如:帮我分析一下 NVDA),避免多次调用不同工具。",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
symbol: { type: "string", description: "美股代码,如 AAPL、NVDA、TSLA" },
|
|
146
|
+
},
|
|
147
|
+
required: ["symbol"],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
handler: async (input) => {
|
|
151
|
+
const parsed = SymbolInput.safeParse(input);
|
|
152
|
+
if (!parsed.success) return err(parsed.error.message);
|
|
153
|
+
const { symbol } = parsed.data;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const [quotes, profile, analyst, news] = await Promise.allSettled([
|
|
157
|
+
fetchUSQuotes([symbol]),
|
|
158
|
+
fetchStockProfile(symbol),
|
|
159
|
+
fetchAnalystRating(symbol),
|
|
160
|
+
fetchStockNews(symbol, 10),
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
return ok({
|
|
164
|
+
symbol,
|
|
165
|
+
quote: quotes.status === "fulfilled" ? quotes.value[0] ?? null : null,
|
|
166
|
+
profile: profile.status === "fulfilled" ? profile.value : null,
|
|
167
|
+
analyst: analyst.status === "fulfilled" ? analyst.value : null,
|
|
168
|
+
news: news.status === "fulfilled" ? news.value : [],
|
|
169
|
+
});
|
|
170
|
+
} catch (e) {
|
|
171
|
+
return err((e as Error).message);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|