fmp-ai-tools 0.2.4-beta.1 → 0.2.4-beta.2

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.
@@ -195,6 +195,20 @@ function limitRows(res, limit2, compare) {
195
195
  const rows = compare ? [...res.data].sort(compare) : res.data;
196
196
  return { ...res, data: rows.slice(0, limit2) };
197
197
  }
198
+ function toCappedToolResponse(res, limit2, options = {}) {
199
+ const total = res.success !== false && Array.isArray(res.data) ? res.data.length : 0;
200
+ const capped = limitRows(res, limit2, options.compare);
201
+ if (total <= limit2) return toToolResponse(capped);
202
+ return JSON.stringify(
203
+ {
204
+ data: capped.data,
205
+ truncated: true,
206
+ note: `Showing ${limit2} of ${total} rows. ${options.hint ?? "Increase `limit` to see more."}`
207
+ },
208
+ null,
209
+ 2
210
+ );
211
+ }
198
212
  function toToolError(error) {
199
213
  const message = error instanceof Error ? error.message : String(error);
200
214
  let type = "unknown";
@@ -276,7 +290,7 @@ var quoteDefinitions = [
276
290
  to: z.string().optional().nullable().describe("End date in YYYY-MM-DD format (optional)"),
277
291
  limit: z.number().int().positive().default(30).describe("Max number of most-recent days to return (default 30)")
278
292
  }),
279
- execute: async ({ symbol: symbol2, from, to, limit: limit2 = 30 }) => {
293
+ execute: async ({ symbol: symbol2, from, to, limit: limit2 }) => {
280
294
  const res = await getFMPClient().quote.getHistoricalPrice({
281
295
  symbol: symbol2,
282
296
  from: from ?? void 0,
@@ -286,7 +300,7 @@ var quoteDefinitions = [
286
300
  if (res.success && data && Array.isArray(data.historical)) {
287
301
  return toToolResponse({
288
302
  ...res,
289
- data: { ...data, historical: data.historical.slice(0, limit2) }
303
+ data: { ...data, historical: data.historical.slice(0, limit2 ?? 30) }
290
304
  });
291
305
  }
292
306
  return toToolResponse(res);
@@ -302,7 +316,7 @@ var quoteDefinitions = [
302
316
  to: z.string().optional().nullable().describe("End date in YYYY-MM-DD format (optional)"),
303
317
  limit: z.number().int().positive().default(50).describe("Max number of most-recent bars to return (default 50)")
304
318
  }),
305
- execute: async ({ symbol: symbol2, interval = "5min", from, to, limit: limit2 = 50 }) => {
319
+ execute: async ({ symbol: symbol2, interval = "5min", from, to, limit: limit2 }) => {
306
320
  const res = await getFMPClient().quote.getIntraday({
307
321
  symbol: symbol2,
308
322
  interval,
@@ -310,7 +324,7 @@ var quoteDefinitions = [
310
324
  to: to ?? void 0
311
325
  });
312
326
  if (res.success && Array.isArray(res.data)) {
313
- return toToolResponse({ ...res, data: res.data.slice(0, limit2) });
327
+ return toToolResponse({ ...res, data: res.data.slice(0, limit2 ?? 50) });
314
328
  }
315
329
  return toToolResponse(res);
316
330
  }
@@ -338,7 +352,7 @@ var companyDefinitions = [
338
352
  inputSchema: symbolSchema.extend({
339
353
  limit: z.number().int().positive().default(20).describe("Max number of compensation records to return (default 20)")
340
354
  }),
341
- execute: async ({ symbol: symbol2, limit: limit2 = 20 }) => toToolResponse(limitRows(await getFMPClient().company.getExecutiveCompensation(symbol2), limit2))
355
+ execute: async ({ symbol: symbol2, limit: limit2 }) => toCappedToolResponse(await getFMPClient().company.getExecutiveCompensation(symbol2), limit2 ?? 20)
342
356
  }),
343
357
  defineTool({
344
358
  name: "getStockPeers",
@@ -354,7 +368,7 @@ var statementTool = (name, description, what, call) => defineTool({
354
368
  name,
355
369
  description,
356
370
  inputSchema: z.object({ symbol: symbol(what), period, limit }),
357
- execute: async ({ symbol: symbol2, period: period2, limit: limit2 }) => toToolResponse(await call({ symbol: symbol2, period: period2, limit: limit2 }))
371
+ execute: async ({ symbol: symbol2, period: period2, limit: limit2 }) => toToolResponse(await call({ symbol: symbol2, period: period2, limit: limit2 ?? void 0 }))
358
372
  });
359
373
  var financialDefinitions = [
360
374
  statementTool(
@@ -424,7 +438,9 @@ var financialDefinitions = [
424
438
  symbol: symbol("earnings historical"),
425
439
  limit: z.number().default(10).describe("The number of periods to retrieve")
426
440
  }),
427
- execute: async ({ symbol: symbol2, limit: limit2 }) => toToolResponse(await getFMPClient().financial.getEarningsHistorical({ symbol: symbol2, limit: limit2 }))
441
+ execute: async ({ symbol: symbol2, limit: limit2 }) => toToolResponse(
442
+ await getFMPClient().financial.getEarningsHistorical({ symbol: symbol2, limit: limit2 ?? 10 })
443
+ )
428
444
  }),
429
445
  defineTool({
430
446
  name: "getFinancialScores",
@@ -469,30 +485,33 @@ var dateRangeSchema = z.object({
469
485
  var calendarDefinitions = [
470
486
  defineTool({
471
487
  name: "getEarningsCalendar",
472
- description: "Get earnings calendar showing upcoming and recent earnings announcements (at most `limit` entries; narrow the date range for full coverage)",
473
- inputSchema: dateRangeSchema,
474
- execute: async ({ from, to, limit: limit2 = 50 }) => toToolResponse(
475
- limitRows(
476
- await getFMPClient().calendar.getEarningsCalendar({
477
- from: from ?? void 0,
478
- to: to ?? void 0
479
- }),
480
- limit2
481
- )
482
- )
488
+ description: "Get earnings calendar showing upcoming and recent earnings announcements, in date order. A week can hold ~2,000 companies worldwide, so pass `symbols` to look up specific companies (e.g. mega-cap tech) instead of scanning the whole calendar.",
489
+ inputSchema: dateRangeSchema.extend({
490
+ symbols: z.array(z.string().min(1)).optional().nullable().describe('Only return these symbols (e.g., ["AAPL", "MSFT", "NVDA"])')
491
+ }),
492
+ execute: async ({ from, to, symbols, limit: limit2 }) => {
493
+ const res = await getFMPClient().calendar.getEarningsCalendar({
494
+ from: from ?? void 0,
495
+ to: to ?? void 0
496
+ });
497
+ const wanted = symbols?.length ? new Set(symbols.map((s) => s.toUpperCase())) : null;
498
+ const filtered = wanted && res.success !== false && Array.isArray(res.data) ? { ...res, data: res.data.filter((row) => wanted.has(row.symbol.toUpperCase())) } : res;
499
+ return toCappedToolResponse(filtered, limit2 ?? 50, {
500
+ hint: "Rows are in date order. Pass `symbols` to find specific companies, or narrow the date range."
501
+ });
502
+ }
483
503
  }),
484
504
  defineTool({
485
505
  name: "getEconomicCalendar",
486
- description: "Get economic calendar showing upcoming and recent economic events and indicators (at most `limit` entries; narrow the date range for full coverage)",
506
+ description: "Get economic calendar showing upcoming and recent economic events and indicators, in date order (at most `limit` entries; narrow the date range for full coverage)",
487
507
  inputSchema: dateRangeSchema,
488
- execute: async ({ from, to, limit: limit2 = 50 }) => toToolResponse(
489
- limitRows(
490
- await getFMPClient().calendar.getEconomicsCalendar({
491
- from: from ?? void 0,
492
- to: to ?? void 0
493
- }),
494
- limit2
495
- )
508
+ execute: async ({ from, to, limit: limit2 }) => toCappedToolResponse(
509
+ await getFMPClient().calendar.getEconomicsCalendar({
510
+ from: from ?? void 0,
511
+ to: to ?? void 0
512
+ }),
513
+ limit2 ?? 50,
514
+ { hint: "Rows are in date order; narrow the date range to see the rest." }
496
515
  )
497
516
  })
498
517
  ];
@@ -559,17 +578,29 @@ var etfDefinitions = [
559
578
  description: "Get the largest holdings of an ETF (by portfolio weight), showing the underlying assets and their weights",
560
579
  inputSchema: z.object({
561
580
  symbol: z.string().min(1, "ETF symbol is required").describe("ETF symbol (e.g., SPY, QQQ, VTI)"),
562
- date: z.string().optional().nullable().describe("Date for holdings in YYYY-MM-DD format (optional)"),
581
+ date: z.string().optional().nullable().describe("Holdings date in YYYY-MM-DD format (optional; defaults to the latest filing)"),
563
582
  limit: z.number().int().positive().default(50).describe("Max number of holdings to return, largest weight first (default 50)")
564
583
  }),
565
- execute: async ({ symbol: symbol2, date, limit: limit2 = 50 }) => {
584
+ execute: async ({ symbol: symbol2, date, limit: limit2 }) => {
585
+ const client = getFMPClient();
586
+ let holdingsDate = date ?? void 0;
587
+ if (!holdingsDate) {
588
+ const dates = await client.etf.getHoldingDates(symbol2);
589
+ if (dates.success !== false && Array.isArray(dates.data)) {
590
+ holdingsDate = dates.data.reduce(
591
+ (latest, d) => !latest || d.date > latest ? d.date : latest,
592
+ void 0
593
+ );
594
+ }
595
+ }
566
596
  const params = { symbol: symbol2 };
567
- if (date) {
568
- params.date = date;
597
+ if (holdingsDate) {
598
+ params.date = holdingsDate;
569
599
  }
570
- return toToolResponse(
571
- limitRows(await getFMPClient().etf.getHoldings(params), limit2, (a, b) => b.pctVal - a.pctVal)
572
- );
600
+ return toCappedToolResponse(await client.etf.getHoldings(params), limit2 ?? 50, {
601
+ compare: (a, b) => b.pctVal - a.pctVal,
602
+ hint: `Largest positions first, as of ${holdingsDate ?? "the requested date"}.`
603
+ });
573
604
  }
574
605
  }),
575
606
  defineTool({
@@ -592,23 +623,36 @@ var insiderDefinitions = [
592
623
  execute: async ({ symbol: symbol2, page }) => toToolResponse(await getFMPClient().insider.getInsiderTradesBySymbol(symbol2, page))
593
624
  })
594
625
  ];
626
+ var YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
627
+ var holderKey = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\b(inc|corp|corporation|co|llc|ltd|lp|plc|the)\b/g, " ").replace(/\s+/g, " ").trim();
628
+ function currentHolders(rows) {
629
+ const newest = rows.reduce((max, r) => Math.max(max, Date.parse(r.dateReported) || 0), 0);
630
+ const latest = /* @__PURE__ */ new Map();
631
+ for (const row of rows) {
632
+ const reported = Date.parse(row.dateReported);
633
+ if (!(reported >= newest - YEAR_MS)) continue;
634
+ const key = holderKey(row.holder);
635
+ const prev = latest.get(key);
636
+ if (!prev || reported > Date.parse(prev.dateReported)) latest.set(key, row);
637
+ }
638
+ return [...latest.values()];
639
+ }
595
640
  var institutionalDefinitions = [
596
641
  defineTool({
597
642
  name: "getInstitutionalHolders",
598
- description: "Get the largest institutional holders of a stock (by shares held), showing which institutions own shares and their holdings",
643
+ description: "Get a stock's largest current institutional holders by shares held (each holder's latest 13F filing from the past year)",
599
644
  inputSchema: z.object({
600
645
  symbol: z.string().min(1, "Stock symbol is required").describe("Stock symbol (e.g., AAPL, MSFT, GOOGL)"),
601
646
  limit: z.number().int().positive().default(20).describe("Max number of holders to return, largest first (default 20)")
602
647
  }),
603
- // FMP returns thousands of holders in no particular order (~1 MB for AAPL),
604
- // so sort by shares held and keep the top `limit`.
605
- execute: async ({ symbol: symbol2, limit: limit2 = 20 }) => toToolResponse(
606
- limitRows(
607
- await getFMPClient().institutional.getInstitutionalHolders({ symbol: symbol2 }),
608
- limit2,
609
- (a, b) => b.shares - a.shares
610
- )
611
- )
648
+ execute: async ({ symbol: symbol2, limit: limit2 }) => {
649
+ const res = await getFMPClient().institutional.getInstitutionalHolders({ symbol: symbol2 });
650
+ const current = res.success !== false && Array.isArray(res.data) ? { ...res, data: currentHolders(res.data) } : res;
651
+ return toCappedToolResponse(current, limit2 ?? 20, {
652
+ compare: (a, b) => b.shares - a.shares,
653
+ hint: "Holders are ordered by shares held, largest first."
654
+ });
655
+ }
612
656
  })
613
657
  ];
614
658
  var empty = z.object({});
@@ -623,7 +667,7 @@ var marketDefinitions = [
623
667
  inputSchema: z.object({
624
668
  limit: z.number().int().positive().default(50).describe("Max number of indices to return (default 50)")
625
669
  }),
626
- execute: async ({ limit: limit2 = 50 }) => toToolResponse(limitRows(await getFMPClient().market.getMarketPerformance(), limit2))
670
+ execute: async ({ limit: limit2 }) => toCappedToolResponse(await getFMPClient().market.getMarketPerformance(), limit2 ?? 50)
627
671
  }),
628
672
  defineTool({
629
673
  name: "getSectorPerformance",
@@ -675,11 +719,11 @@ var newsDefinitions = [
675
719
  to: z.string().optional().nullable().describe("End date in YYYY-MM-DD format (optional)"),
676
720
  limit: z.number().int().positive().default(20).describe("Max number of articles to return (default 20)")
677
721
  }),
678
- execute: async ({ from, to, limit: limit2 = 20 }) => toToolResponse(
722
+ execute: async ({ from, to, limit: limit2 }) => toToolResponse(
679
723
  await getFMPClient().news.getStockNews({
680
724
  from: from ?? void 0,
681
725
  to: to ?? void 0,
682
- limit: limit2
726
+ limit: limit2 ?? 20
683
727
  })
684
728
  )
685
729
  }),
@@ -692,12 +736,12 @@ var newsDefinitions = [
692
736
  to: z.string().optional().nullable().describe("End date in YYYY-MM-DD format (optional)"),
693
737
  limit: z.number().int().positive().default(20).describe("Max number of articles to return (default 20)")
694
738
  }),
695
- execute: async ({ symbols, from, to, limit: limit2 = 20 }) => toToolResponse(
739
+ execute: async ({ symbols, from, to, limit: limit2 }) => toToolResponse(
696
740
  await getFMPClient().news.getStockNewsBySymbol({
697
741
  symbols,
698
742
  from: from ?? void 0,
699
743
  to: to ?? void 0,
700
- limit: limit2
744
+ limit: limit2 ?? 20
701
745
  })
702
746
  )
703
747
  })
@@ -719,8 +763,8 @@ var screenerDefinitions = [
719
763
  isActivelyTrading: z.boolean().optional().nullable().describe("Restrict to actively trading"),
720
764
  limit: z.number().int().positive().default(50).describe("Max number of results to return (default 50)")
721
765
  }),
722
- execute: async ({ limit: limit2 = 50, ...filters }) => {
723
- const params = { limit: limit2 };
766
+ execute: async ({ limit: limit2, ...filters }) => {
767
+ const params = { limit: limit2 ?? 50 };
724
768
  for (const [key, value] of Object.entries(filters)) {
725
769
  if (value !== null && value !== void 0) params[key] = value;
726
770
  }
@@ -737,8 +781,12 @@ var searchDefinitions = [
737
781
  limit: z.number().int().positive().default(10).describe("Max number of results to return (default 10)"),
738
782
  exchange: z.string().optional().nullable().describe('Restrict to a specific exchange (optional, e.g., "NASDAQ")')
739
783
  }),
740
- execute: async ({ query, limit: limit2 = 10, exchange }) => toToolResponse(
741
- await getFMPClient().search.search({ query, limit: limit2, exchange: exchange ?? void 0 })
784
+ execute: async ({ query, limit: limit2, exchange }) => toToolResponse(
785
+ await getFMPClient().search.search({
786
+ query,
787
+ limit: limit2 ?? 10,
788
+ exchange: exchange ?? void 0
789
+ })
742
790
  )
743
791
  })
744
792
  ];
@@ -751,7 +799,9 @@ var analystDefinitions = [
751
799
  period: z.enum(["annual", "quarter"]).default("annual").describe("The period type (annual or quarter)"),
752
800
  limit: z.number().int().positive().default(10).describe("Max number of periods to return (default 10)")
753
801
  }),
754
- execute: async ({ symbol: symbol2, period: period2 = "annual", limit: limit2 = 10 }) => toToolResponse(await getFMPClient().analyst.getEstimates({ symbol: symbol2, period: period2, limit: limit2 }))
802
+ execute: async ({ symbol: symbol2, period: period2 = "annual", limit: limit2 }) => toToolResponse(
803
+ await getFMPClient().analyst.getEstimates({ symbol: symbol2, period: period2, limit: limit2 ?? 10 })
804
+ )
755
805
  }),
756
806
  defineTool({
757
807
  name: "getPriceTargetConsensus",
@@ -768,10 +818,10 @@ var analystDefinitions = [
768
818
  symbol: z.string().min(1).describe("The stock symbol (e.g., AAPL)"),
769
819
  limit: z.number().int().positive().default(20).describe("Max number of grade entries to return (default 20)")
770
820
  }),
771
- execute: async ({ symbol: symbol2, limit: limit2 = 20 }) => {
821
+ execute: async ({ symbol: symbol2, limit: limit2 }) => {
772
822
  const res = await getFMPClient().analyst.getGrades({ symbol: symbol2 });
773
823
  if (res.success && Array.isArray(res.data)) {
774
- return toToolResponse({ ...res, data: res.data.slice(0, limit2) });
824
+ return toToolResponse({ ...res, data: res.data.slice(0, limit2 ?? 20) });
775
825
  }
776
826
  return toToolResponse(res);
777
827
  }
@@ -813,7 +863,7 @@ var technicalDefinitions = [
813
863
  timeframe: z.enum(["1min", "5min", "15min", "30min", "1hour", "4hour", "1day", "1week", "1month"]).default("1day").describe("Bar timeframe"),
814
864
  limit: z.number().int().positive().default(50).describe("Max number of most-recent bars to return (default 50)")
815
865
  }),
816
- execute: async ({ symbol: symbol2, type, periodLength = 10, timeframe = "1day", limit: limit2 = 50 }) => {
866
+ execute: async ({ symbol: symbol2, type, periodLength = 10, timeframe = "1day", limit: limit2 }) => {
817
867
  const res = await getFMPClient().technical.getTechnicalIndicator({
818
868
  symbol: symbol2,
819
869
  type,
@@ -821,7 +871,7 @@ var technicalDefinitions = [
821
871
  timeframe
822
872
  });
823
873
  if (res.success && Array.isArray(res.data)) {
824
- return toToolResponse({ ...res, data: res.data.slice(0, limit2) });
874
+ return toToolResponse({ ...res, data: res.data.slice(0, limit2 ?? 50) });
825
875
  }
826
876
  return toToolResponse(res);
827
877
  }
@@ -854,8 +904,9 @@ var senateHouseDefinitions = [
854
904
  name: z.string().min(1, "Name is required").describe("The name of the senator to get trading data for"),
855
905
  limit: byNameLimit
856
906
  }),
857
- execute: async ({ name, limit: limit2 = 50 }) => toToolResponse(
858
- limitRows(await getFMPClient().senateHouse.getSenateTradingByName({ name }), limit2)
907
+ execute: async ({ name, limit: limit2 }) => toCappedToolResponse(
908
+ await getFMPClient().senateHouse.getSenateTradingByName({ name }),
909
+ limit2 ?? 50
859
910
  )
860
911
  }),
861
912
  defineTool({
@@ -865,8 +916,9 @@ var senateHouseDefinitions = [
865
916
  name: z.string().min(1, "Name is required").describe("The name of the representative to get trading data for"),
866
917
  limit: byNameLimit
867
918
  }),
868
- execute: async ({ name, limit: limit2 = 50 }) => toToolResponse(
869
- limitRows(await getFMPClient().senateHouse.getHouseTradingByName({ name }), limit2)
919
+ execute: async ({ name, limit: limit2 }) => toCappedToolResponse(
920
+ await getFMPClient().senateHouse.getHouseTradingByName({ name }),
921
+ limit2 ?? 50
870
922
  )
871
923
  }),
872
924
  defineTool({