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