fdic-mcp-server 1.7.6 → 1.8.1

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
@@ -32,6 +32,8 @@ The FDIC BankFind Suite API is public and useful, but it is not packaged for MCP
32
32
 
33
33
  ## Documentation
34
34
 
35
+ Public user docs:
36
+
35
37
  - [GitHub Pages docs entry point](https://jflamb.github.io/fdic-mcp-server/)
36
38
  - [Hosted MCP endpoint](https://bankfind.jflamb.com/mcp)
37
39
  - [Local docs home](./docs/index.md)
@@ -42,9 +44,18 @@ The FDIC BankFind Suite API is public and useful, but it is not packaged for MCP
42
44
  - [Client setup](./docs/clients.md)
43
45
  - [Troubleshooting and FAQ](./docs/troubleshooting.md)
44
46
  - [Compatibility matrix](./docs/compatibility-matrix.md)
45
- - [Technical specification](./docs/technical/specification.md)
46
- - [Architecture](./docs/technical/architecture.md)
47
- - [Key decisions](./docs/technical/decisions.md)
47
+
48
+ Repo reference docs:
49
+
50
+ - [Reference home](./reference/README.md)
51
+ - [Technical specification](./reference/specification.md)
52
+ - [Architecture](./reference/architecture.md)
53
+ - [Key decisions](./reference/decisions.md)
54
+ - [Cloud Run deployment](./reference/cloud-run-deployment.md)
55
+ - [Plans and design notes](./reference/plans/README.md)
56
+
57
+ Project and release info:
58
+
48
59
  - [Release history](https://github.com/jflamb/fdic-mcp-server/releases)
49
60
  - [Archived release notes](./docs/release-notes/index.md)
50
61
  - [Security policy](./SECURITY.md)
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
32
32
  var import_express2 = __toESM(require("express"));
33
33
 
34
34
  // src/constants.ts
35
- var VERSION = true ? "1.7.6" : process.env.npm_package_version ?? "0.0.0-dev";
35
+ var VERSION = true ? "1.8.1" : process.env.npm_package_version ?? "0.0.0-dev";
36
36
  var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
37
37
  var CHARACTER_LIMIT = 5e4;
38
38
  var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
@@ -31850,10 +31850,10 @@ function formatToolError(err) {
31850
31850
  var import_zod = require("zod");
31851
31851
  var CommonQuerySchema = import_zod.z.object({
31852
31852
  filters: import_zod.z.string().optional().describe(
31853
- 'ElasticSearch query string filter. Examples: STNAME:"California", ACTIVE:1 AND ASSET:[1000000 TO *], NAME:"Chase"'
31853
+ 'FDIC API filter using ElasticSearch query string syntax. Combine conditions with AND/OR, use quotes for multi-word values, and [min TO max] for ranges (* = unbounded). Common fields: NAME (institution name), STNAME (state name), STALP (two-letter state code), CERT (certificate number), ASSET (total assets in $thousands), ACTIVE (1=active, 0=inactive). Examples: STNAME:"California", ACTIVE:1 AND ASSET:[1000000 TO *], NAME:"Chase"'
31854
31854
  ),
31855
31855
  fields: import_zod.z.string().optional().describe(
31856
- "Comma-separated list of fields to return. Leave empty to return all fields. Example: NAME,CERT,ASSET,DEP,STALP"
31856
+ "Comma-separated list of FDIC field names to return. Leave empty to return all fields. Field names are ALL_CAPS (e.g., NAME, CERT, ASSET, DEP, STALP). Example: NAME,CERT,ASSET,DEP,STALP"
31857
31857
  ),
31858
31858
  limit: import_zod.z.number().int().min(1).max(1e4).default(20).describe("Maximum number of records to return (1-10000, default: 20)"),
31859
31859
  offset: import_zod.z.number().int().min(0).default(0).describe("Number of records to skip for pagination (default: 0)"),
@@ -31889,18 +31889,26 @@ Common filter examples:
31889
31889
  - Savings institutions: MUTUAL:1
31890
31890
  - Recently established: ESTYMD:[2010-01-01 TO *]
31891
31891
 
31892
+ Charter class codes (BKCLASS):
31893
+ N = National commercial bank (OCC-supervised)
31894
+ SM = State-chartered, Federal Reserve member
31895
+ NM = State-chartered, non-member (FDIC-supervised)
31896
+ SB = Federal savings bank (OCC-supervised)
31897
+ SA = State savings association
31898
+ OI = Insured branch of foreign bank
31899
+
31892
31900
  Key returned fields:
31893
31901
  - CERT: FDIC Certificate Number (unique ID)
31894
31902
  - NAME: Institution name
31895
- - CITY, STALP, STNAME: Location
31903
+ - CITY, STALP (two-letter state code), STNAME (full state name): Location
31896
31904
  - ASSET: Total assets ($thousands)
31897
31905
  - DEP: Total deposits ($thousands)
31898
- - BKCLASS: Charter class code
31906
+ - BKCLASS: Charter class code (see above)
31899
31907
  - ACTIVE: 1 if currently active, 0 if inactive
31900
31908
  - ROA, ROE: Profitability ratios
31901
31909
  - OFFICES: Number of branch offices
31902
- - ESTYMD: Establishment date
31903
- - REGAGNT: Primary federal regulator
31910
+ - ESTYMD: Establishment date (YYYY-MM-DD)
31911
+ - REGAGNT: Primary federal regulator (OCC, FRS, FDIC)
31904
31912
 
31905
31913
  Args:
31906
31914
  - filters (string, optional): ElasticSearch query filter
@@ -32023,22 +32031,27 @@ function registerFailureTools(server) {
32023
32031
  Returns data on bank failures including failure date, resolution type, estimated cost to the FDIC Deposit Insurance Fund, and acquiring institution info.
32024
32032
 
32025
32033
  Common filter examples:
32026
- - By state: STALP:CA
32034
+ - By state: STALP:CA (two-letter state code)
32027
32035
  - By year range: FAILDATE:[2008-01-01 TO 2010-12-31]
32028
32036
  - Recent failures: FAILDATE:[2020-01-01 TO *]
32029
- - By resolution type: RESTYPE:PAYOFF or RESTYPE:MERGER
32037
+ - By resolution type: RESTYPE:PAYOFF or RESTYPE:"PURCHASE AND ASSUMPTION"
32030
32038
  - Large failures by cost: COST:[100000 TO *] (cost in $thousands)
32031
32039
  - By name: NAME:"Washington Mutual"
32032
32040
 
32041
+ Resolution types (RESTYPE):
32042
+ PAYOFF = depositors paid directly, no acquirer
32043
+ PURCHASE AND ASSUMPTION = acquirer buys assets and assumes deposits
32044
+ PAYOUT = variant of payoff with insured-deposit transfer
32045
+
32033
32046
  Key returned fields:
32034
32047
  - CERT: FDIC Certificate Number
32035
32048
  - NAME: Institution name
32036
- - CITY, STALP, STNAME: Location
32049
+ - CITY, STALP (two-letter state code), STNAME (full state name): Location
32037
32050
  - FAILDATE: Date of failure (YYYY-MM-DD)
32038
- - SAVR: Savings rate at failure
32039
- - RESTYPE: Resolution type (PAYOFF, MERGER, PURCHASE & ASSUMPTION, etc.)
32051
+ - SAVR: Savings association flag (SA) or bank (BK)
32052
+ - RESTYPE: Resolution type (see above)
32040
32053
  - QBFASSET: Total assets at failure ($thousands)
32041
- - COST: Estimated cost to FDIC DIF ($thousands)
32054
+ - COST: Estimated cost to FDIC Deposit Insurance Fund ($thousands)
32042
32055
 
32043
32056
  Args:
32044
32057
  - filters (string, optional): ElasticSearch query filter
@@ -32158,6 +32171,27 @@ var import_zod2 = require("zod");
32158
32171
  var CHUNK_SIZE = 25;
32159
32172
  var MAX_CONCURRENCY = 4;
32160
32173
  var ANALYSIS_TIMEOUT_MS = 9e4;
32174
+ function getDefaultReportDate() {
32175
+ const target = new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
32176
+ const year = target.getFullYear();
32177
+ const month = target.getMonth() + 1;
32178
+ if (month >= 10) return `${year}0930`;
32179
+ if (month >= 7) return `${year}0630`;
32180
+ if (month >= 4) return `${year}0331`;
32181
+ return `${year - 1}1231`;
32182
+ }
32183
+ function getReportDateOneYearPrior(repdte) {
32184
+ const year = Number.parseInt(repdte.slice(0, 4), 10);
32185
+ return `${year - 1}${repdte.slice(4)}`;
32186
+ }
32187
+ var VALID_QUARTER_END_SUFFIXES = /* @__PURE__ */ new Set(["0331", "0630", "0930", "1231"]);
32188
+ function validateQuarterEndDate(repdte, label) {
32189
+ const suffix = repdte.slice(4);
32190
+ if (!VALID_QUARTER_END_SUFFIXES.has(suffix)) {
32191
+ return `${label} "${repdte}" is not a valid quarter-end date. FDIC data is published quarterly \u2014 use a date ending in 0331 (Q1), 0630 (Q2), 0930 (Q3), or 1231 (Q4). Example: ${repdte.slice(0, 4)}1231 for Q4 ${repdte.slice(0, 4)}.`;
32192
+ }
32193
+ return null;
32194
+ }
32161
32195
  function asNumber(value) {
32162
32196
  return typeof value === "number" ? value : null;
32163
32197
  }
@@ -32226,23 +32260,34 @@ Returns branch/office data including address, city, state, coordinates, branch t
32226
32260
 
32227
32261
  Common filter examples:
32228
32262
  - All branches of a bank: CERT:3511
32229
- - By state: STALP:TX
32263
+ - By state: STALP:TX (two-letter state code)
32230
32264
  - By city: CITY:"Austin"
32231
32265
  - Main offices only: BRNUM:0
32232
32266
  - By county: COUNTY:"Travis"
32233
- - Active branches: ENDEFYMD:[9999-01-01 TO *]
32234
- - By CBSA (metro area): CBSA_METRO_NAME:"New York-Newark-Jersey City"
32267
+ - Active branches only: ENDEFYMD:[9999-01-01 TO *] (sentinel date 9999-12-31 means still open)
32268
+ - By metro area (CBSA): CBSA_METRO_NAME:"New York-Newark-Jersey City"
32269
+
32270
+ Branch service types (BRSERTYP):
32271
+ 11 = Full service brick and mortar
32272
+ 12 = Full service retail
32273
+ 21 = Limited service administrative
32274
+ 22 = Limited service military
32275
+ 23 = Limited service drive-through
32276
+ 24 = Limited service loan production
32277
+ 25 = Limited service consumer/trust
32278
+ 26 = Limited service Internet/mobile
32279
+ 29 = Limited service other
32235
32280
 
32236
32281
  Key returned fields:
32237
32282
  - CERT: FDIC Certificate Number
32238
32283
  - UNINAME: Institution name
32239
32284
  - NAMEFULL: Full branch name
32240
- - ADDRESS, CITY, STALP, ZIP: Branch address
32285
+ - ADDRESS, CITY, STALP (two-letter state code), ZIP: Branch address
32241
32286
  - COUNTY: County name
32242
32287
  - BRNUM: Branch number (0 = main office)
32243
- - BRSERTYP: Branch service type
32288
+ - BRSERTYP: Branch service type code (see above)
32244
32289
  - LATITUDE, LONGITUDE: Geographic coordinates
32245
- - ESTYMD: Branch established date
32290
+ - ESTYMD: Branch established date (YYYY-MM-DD)
32246
32291
  - ENDEFYMD: Branch end date (9999-12-31 if still active)
32247
32292
 
32248
32293
  Args:
@@ -32323,22 +32368,36 @@ Common filter examples:
32323
32368
  - History for a specific bank: CERT:3511
32324
32369
  - Mergers: TYPE:merger
32325
32370
  - Failures: TYPE:failure
32326
- - Name changes: CHANGECODE:CO (name change code)
32371
+ - Name changes: CHANGECODE:CO
32327
32372
  - By date range: PROCDATE:[2008-01-01 TO 2009-12-31]
32328
- - By state: PSTALP:CA
32373
+ - By state: PSTALP:CA (two-letter state code)
32374
+
32375
+ Event types (TYPE):
32376
+ merger = institution was merged into another
32377
+ failure = institution failed
32378
+ assistance = received FDIC assistance transaction
32379
+ insurance = insurance-related event (new coverage, termination)
32380
+
32381
+ Common change codes (CHANGECODE):
32382
+ CO = name change
32383
+ CR = charter conversion
32384
+ DC = deposit assumption change
32385
+ MA = merger/acquisition (absorbed by another institution)
32386
+ NI = new institution insured
32387
+ TC = trust company conversion
32329
32388
 
32330
32389
  Key returned fields:
32331
32390
  - CERT: FDIC Certificate Number
32332
32391
  - INSTNAME: Institution name
32333
32392
  - CLASS: Charter class at time of change
32334
- - PCITY, PSTALP: Location (city, state abbreviation)
32335
- - PROCDATE: Processing date of the change
32336
- - EFFDATE: Effective date of the change
32393
+ - PCITY, PSTALP: Location (city, two-letter state code)
32394
+ - PROCDATE: Processing date of the change (YYYY-MM-DD)
32395
+ - EFFDATE: Effective date of the change (YYYY-MM-DD)
32337
32396
  - ENDEFYMD: End effective date
32338
32397
  - PCERT: Predecessor/successor CERT (for mergers)
32339
- - TYPE: Type of structural change
32340
- - CHANGECODE: Code for type of change
32341
- - CHANGECODE_DESC: Description of change code
32398
+ - TYPE: Type of structural change (see above)
32399
+ - CHANGECODE: Code for type of change (see above)
32400
+ - CHANGECODE_DESC: Human-readable description of the change code
32342
32401
  - INSDATE: Insurance date
32343
32402
 
32344
32403
  Args:
@@ -32409,7 +32468,7 @@ var FinancialQuerySchema = CommonQuerySchema.extend({
32409
32468
  "Filter by FDIC Certificate Number to get financials for a specific institution"
32410
32469
  ),
32411
32470
  repdte: import_zod4.z.string().optional().describe(
32412
- "Filter by report date in YYYYMMDD format (quarterly call report dates). Example: 20231231 for Q4 2023"
32471
+ "Filter by Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on call report dates: March 31, June 30, September 30, and December 31. Example: 20231231 for Q4 2023. If omitted, returns all available dates (sorted most recent first by default)."
32413
32472
  )
32414
32473
  });
32415
32474
  var SummaryQuerySchema = CommonQuerySchema.extend({
@@ -32434,7 +32493,7 @@ Common filter examples:
32434
32493
 
32435
32494
  Key returned fields:
32436
32495
  - CERT: FDIC Certificate Number
32437
- - REPDTE: Report date (YYYYMMDD)
32496
+ - REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
32438
32497
  - ASSET: Total assets ($thousands)
32439
32498
  - DEP: Total deposits ($thousands)
32440
32499
  - DEPDOM: Domestic deposits ($thousands)
@@ -32447,7 +32506,7 @@ Key returned fields:
32447
32506
 
32448
32507
  Args:
32449
32508
  - cert (number, optional): Filter by institution CERT number
32450
- - repdte (string, optional): Report date in YYYYMMDD format
32509
+ - repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
32451
32510
  - filters (string, optional): Additional ElasticSearch query filters
32452
32511
  - fields (string, optional): Comma-separated field names (the full set has 1,100+ fields)
32453
32512
  - limit (number): Records to return (default: 20)
@@ -32527,7 +32586,7 @@ Key returned fields:
32527
32586
  - ROA: Return on assets (%)
32528
32587
  - ROE: Return on equity (%)
32529
32588
  - OFFICES: Number of branch offices
32530
- - REPDTE: Report date
32589
+ - REPDTE: Report Date \u2014 the last day of the reporting period (YYYYMMDD)
32531
32590
 
32532
32591
  Args:
32533
32592
  - cert (number, optional): Filter by institution CERT number
@@ -32692,7 +32751,7 @@ var import_zod6 = require("zod");
32692
32751
  var DemographicsQuerySchema = CommonQuerySchema.extend({
32693
32752
  cert: import_zod6.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
32694
32753
  repdte: import_zod6.z.string().optional().describe(
32695
- "Filter by report date in YYYYMMDD format. Example: 20251231"
32754
+ "Filter by Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on: March 31, June 30, September 30, and December 31. Example: 20251231 for Q4 2025. If omitted, returns all available dates."
32696
32755
  )
32697
32756
  });
32698
32757
  function registerDemographicsTools(server) {
@@ -32713,7 +32772,7 @@ Common filter examples:
32713
32772
 
32714
32773
  Key returned fields:
32715
32774
  - CERT: FDIC Certificate Number
32716
- - REPDTE: Report date (YYYYMMDD)
32775
+ - REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
32717
32776
  - QTRNO: Quarter number
32718
32777
  - OFFTOT: Total offices
32719
32778
  - OFFSTATE: Offices in other states
@@ -32727,7 +32786,7 @@ Key returned fields:
32727
32786
 
32728
32787
  Args:
32729
32788
  - cert (number, optional): Filter by institution CERT number
32730
- - repdte (string, optional): Report date in YYYYMMDD format
32789
+ - repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
32731
32790
  - filters (string, optional): Additional ElasticSearch query filters
32732
32791
  - fields (string, optional): Comma-separated field names
32733
32792
  - limit (number): Records to return (default: 20)
@@ -32838,8 +32897,12 @@ var SnapshotAnalysisSchema = import_zod7.z.object({
32838
32897
  'Additional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"'
32839
32898
  ),
32840
32899
  active_only: import_zod7.z.boolean().default(true).describe("Limit the comparison set to currently active institutions."),
32841
- start_repdte: import_zod7.z.string().regex(/^\d{8}$/).describe("Starting report date in YYYYMMDD format."),
32842
- end_repdte: import_zod7.z.string().regex(/^\d{8}$/).describe("Ending report date in YYYYMMDD format."),
32900
+ start_repdte: import_zod7.z.string().regex(/^\d{8}$/).optional().describe(
32901
+ "Starting Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Example: 20210331 for Q1 2021. If omitted, defaults to the same quarter one year before end_repdte."
32902
+ ),
32903
+ end_repdte: import_zod7.z.string().regex(/^\d{8}$/).optional().describe(
32904
+ "Ending Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Must be later than start_repdte. Example: 20251231 for Q4 2025. If omitted, defaults to the most recent quarter-end date with published data (~90-day lag)."
32905
+ ),
32843
32906
  analysis_mode: AnalysisModeSchema.default("snapshot").describe(
32844
32907
  "Use snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range."
32845
32908
  ),
@@ -32848,14 +32911,23 @@ var SnapshotAnalysisSchema = import_zod7.z.object({
32848
32911
  ),
32849
32912
  limit: import_zod7.z.number().int().min(1).max(100).default(10).describe("Maximum number of ranked comparisons to return."),
32850
32913
  sort_by: SortFieldSchema.default("asset_growth").describe(
32851
- "Comparison field used to rank institutions."
32914
+ "Comparison field used to rank institutions. Valid options: asset_growth, asset_growth_pct, dep_growth, dep_growth_pct, netinc_change, netinc_change_pct, roa_change, roe_change, offices_change, assets_per_office_change, deposits_per_office_change, deposits_to_assets_change."
32852
32915
  ),
32853
32916
  sort_order: import_zod7.z.enum(["ASC", "DESC"]).default("DESC").describe("Sort direction for the ranked comparisons.")
32854
32917
  });
32918
+ function resolveSnapshotDefaults(params) {
32919
+ const end_repdte = params.end_repdte ?? getDefaultReportDate();
32920
+ const start_repdte = params.start_repdte ?? getReportDateOneYearPrior(end_repdte);
32921
+ return { ...params, start_repdte, end_repdte };
32922
+ }
32855
32923
  function validateSnapshotAnalysisParams(value) {
32856
32924
  if (!value.state && (!value.certs || value.certs.length === 0)) {
32857
32925
  return "Provide either state or certs.";
32858
32926
  }
32927
+ const startDateError = validateQuarterEndDate(value.start_repdte, "start_repdte");
32928
+ if (startDateError) return startDateError;
32929
+ const endDateError = validateQuarterEndDate(value.end_repdte, "end_repdte");
32930
+ if (endDateError) return endDateError;
32859
32931
  if (value.start_repdte >= value.end_repdte) {
32860
32932
  return "start_repdte must be earlier than end_repdte.";
32861
32933
  }
@@ -33381,12 +33453,12 @@ Good uses:
33381
33453
 
33382
33454
  Inputs:
33383
33455
  - state or certs: choose a geographic roster or provide a direct comparison set
33384
- - start_repdte, end_repdte: report dates in YYYYMMDD format
33456
+ - start_repdte, end_repdte: Report Dates (REPDTE) in YYYYMMDD format \u2014 must be quarter-end dates (0331, 0630, 0930, 1231)
33385
33457
  - analysis_mode: snapshot or timeseries
33386
33458
  - institution_filters: optional extra institution filter when building the roster
33387
33459
  - active_only: default true
33388
33460
  - include_demographics: default true, adds office-count comparisons when available
33389
- - sort_by: ranking field such as asset_growth, dep_growth_pct, roa_change, assets_per_office_change
33461
+ - sort_by: ranking field (default: asset_growth). All options: asset_growth, asset_growth_pct, dep_growth, dep_growth_pct, netinc_change, netinc_change_pct, roa_change, roe_change, offices_change, assets_per_office_change, deposits_per_office_change, deposits_to_assets_change
33390
33462
  - sort_order: ASC or DESC
33391
33463
  - limit: maximum ranked results to return
33392
33464
 
@@ -33399,19 +33471,20 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
33399
33471
  openWorldHint: true
33400
33472
  }
33401
33473
  },
33402
- async ({
33403
- state,
33404
- certs,
33405
- institution_filters,
33406
- active_only,
33407
- start_repdte,
33408
- end_repdte,
33409
- analysis_mode,
33410
- include_demographics,
33411
- limit,
33412
- sort_by,
33413
- sort_order
33414
- }, extra) => {
33474
+ async (rawParams, extra) => {
33475
+ const {
33476
+ state,
33477
+ certs,
33478
+ institution_filters,
33479
+ active_only,
33480
+ start_repdte,
33481
+ end_repdte,
33482
+ analysis_mode,
33483
+ include_demographics,
33484
+ limit,
33485
+ sort_by,
33486
+ sort_order
33487
+ } = resolveSnapshotDefaults(rawParams);
33415
33488
  const controller = new AbortController();
33416
33489
  const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
33417
33490
  const progressToken = extra._meta?.progressToken;
@@ -33631,7 +33704,7 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
33631
33704
  if (controller.signal.aborted) {
33632
33705
  return formatToolError(
33633
33706
  new Error(
33634
- `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Narrow the comparison set with certs or institution_filters and try again.`
33707
+ `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Try reducing the comparison set: use certs (max 100) instead of a state-wide roster, add institution_filters (e.g., BKCLASS:N), or shorten the date range for timeseries mode.`
33635
33708
  )
33636
33709
  );
33637
33710
  }
@@ -33792,7 +33865,9 @@ var PeerGroupInputSchema = import_zod8.z.object({
33792
33865
  cert: import_zod8.z.number().int().positive().optional().describe(
33793
33866
  "Subject institution CERT number. When provided, auto-derives peer criteria and ranks this bank against peers."
33794
33867
  ),
33795
- repdte: import_zod8.z.string().regex(/^\d{8}$/).describe("Report date in YYYYMMDD format."),
33868
+ repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
33869
+ "Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on: March 31, June 30, September 30, and December 31. Example: 20231231 for Q4 2023. If omitted, defaults to the most recent quarter-end date likely to have published data (~90-day lag)."
33870
+ ),
33796
33871
  asset_min: import_zod8.z.number().positive().optional().describe(
33797
33872
  "Minimum total assets ($thousands) for peer selection. Defaults to 50% of subject's report-date assets when cert is provided."
33798
33873
  ),
@@ -33948,7 +34023,8 @@ Override precedence: cert derives defaults, then explicit params override them.`
33948
34023
  openWorldHint: true
33949
34024
  }
33950
34025
  },
33951
- async (params, extra) => {
34026
+ async (rawParams, extra) => {
34027
+ const params = { ...rawParams, repdte: rawParams.repdte ?? getDefaultReportDate() };
33952
34028
  const controller = new AbortController();
33953
34029
  const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
33954
34030
  const progressToken = extra._meta?.progressToken;
@@ -33957,6 +34033,10 @@ Override precedence: cert derives defaults, then explicit params override them.`
33957
34033
  if (validationError) {
33958
34034
  return formatToolError(new Error(validationError));
33959
34035
  }
34036
+ const dateError = validateQuarterEndDate(params.repdte, "repdte");
34037
+ if (dateError) {
34038
+ return formatToolError(new Error(dateError));
34039
+ }
33960
34040
  const extraFieldsError = validateExtraFields(params.extra_fields);
33961
34041
  if (extraFieldsError) {
33962
34042
  return formatToolError(extraFieldsError);
@@ -34004,7 +34084,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
34004
34084
  if (financialRecords.length === 0) {
34005
34085
  return formatToolError(
34006
34086
  new Error(
34007
- `No financial data for CERT ${params.cert} at report date ${params.repdte}. Auto-derivation of peer criteria requires asset data at the specified report date.`
34087
+ `No financial data for CERT ${params.cert} at report date ${params.repdte}. FDIC quarterly data is published ~90 days after each quarter-end (March 31, June 30, September 30, December 31). Try an earlier quarter-end date, or verify the institution was active at that date.`
34008
34088
  )
34009
34089
  );
34010
34090
  }
@@ -34259,7 +34339,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
34259
34339
  if (controller.signal.aborted) {
34260
34340
  return formatToolError(
34261
34341
  new Error(
34262
- `Peer group analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Narrow the peer group criteria and try again.`
34342
+ `Peer group analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Try narrowing the peer group: add a state filter, tighten the asset_min/asset_max range, or specify charter_classes.`
34263
34343
  )
34264
34344
  );
34265
34345
  }
package/dist/server.js CHANGED
@@ -46,7 +46,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
46
46
  var import_express2 = __toESM(require("express"));
47
47
 
48
48
  // src/constants.ts
49
- var VERSION = true ? "1.7.6" : process.env.npm_package_version ?? "0.0.0-dev";
49
+ var VERSION = true ? "1.8.1" : process.env.npm_package_version ?? "0.0.0-dev";
50
50
  var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
51
51
  var CHARACTER_LIMIT = 5e4;
52
52
  var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
@@ -31864,10 +31864,10 @@ function formatToolError(err) {
31864
31864
  var import_zod = require("zod");
31865
31865
  var CommonQuerySchema = import_zod.z.object({
31866
31866
  filters: import_zod.z.string().optional().describe(
31867
- 'ElasticSearch query string filter. Examples: STNAME:"California", ACTIVE:1 AND ASSET:[1000000 TO *], NAME:"Chase"'
31867
+ 'FDIC API filter using ElasticSearch query string syntax. Combine conditions with AND/OR, use quotes for multi-word values, and [min TO max] for ranges (* = unbounded). Common fields: NAME (institution name), STNAME (state name), STALP (two-letter state code), CERT (certificate number), ASSET (total assets in $thousands), ACTIVE (1=active, 0=inactive). Examples: STNAME:"California", ACTIVE:1 AND ASSET:[1000000 TO *], NAME:"Chase"'
31868
31868
  ),
31869
31869
  fields: import_zod.z.string().optional().describe(
31870
- "Comma-separated list of fields to return. Leave empty to return all fields. Example: NAME,CERT,ASSET,DEP,STALP"
31870
+ "Comma-separated list of FDIC field names to return. Leave empty to return all fields. Field names are ALL_CAPS (e.g., NAME, CERT, ASSET, DEP, STALP). Example: NAME,CERT,ASSET,DEP,STALP"
31871
31871
  ),
31872
31872
  limit: import_zod.z.number().int().min(1).max(1e4).default(20).describe("Maximum number of records to return (1-10000, default: 20)"),
31873
31873
  offset: import_zod.z.number().int().min(0).default(0).describe("Number of records to skip for pagination (default: 0)"),
@@ -31903,18 +31903,26 @@ Common filter examples:
31903
31903
  - Savings institutions: MUTUAL:1
31904
31904
  - Recently established: ESTYMD:[2010-01-01 TO *]
31905
31905
 
31906
+ Charter class codes (BKCLASS):
31907
+ N = National commercial bank (OCC-supervised)
31908
+ SM = State-chartered, Federal Reserve member
31909
+ NM = State-chartered, non-member (FDIC-supervised)
31910
+ SB = Federal savings bank (OCC-supervised)
31911
+ SA = State savings association
31912
+ OI = Insured branch of foreign bank
31913
+
31906
31914
  Key returned fields:
31907
31915
  - CERT: FDIC Certificate Number (unique ID)
31908
31916
  - NAME: Institution name
31909
- - CITY, STALP, STNAME: Location
31917
+ - CITY, STALP (two-letter state code), STNAME (full state name): Location
31910
31918
  - ASSET: Total assets ($thousands)
31911
31919
  - DEP: Total deposits ($thousands)
31912
- - BKCLASS: Charter class code
31920
+ - BKCLASS: Charter class code (see above)
31913
31921
  - ACTIVE: 1 if currently active, 0 if inactive
31914
31922
  - ROA, ROE: Profitability ratios
31915
31923
  - OFFICES: Number of branch offices
31916
- - ESTYMD: Establishment date
31917
- - REGAGNT: Primary federal regulator
31924
+ - ESTYMD: Establishment date (YYYY-MM-DD)
31925
+ - REGAGNT: Primary federal regulator (OCC, FRS, FDIC)
31918
31926
 
31919
31927
  Args:
31920
31928
  - filters (string, optional): ElasticSearch query filter
@@ -32037,22 +32045,27 @@ function registerFailureTools(server) {
32037
32045
  Returns data on bank failures including failure date, resolution type, estimated cost to the FDIC Deposit Insurance Fund, and acquiring institution info.
32038
32046
 
32039
32047
  Common filter examples:
32040
- - By state: STALP:CA
32048
+ - By state: STALP:CA (two-letter state code)
32041
32049
  - By year range: FAILDATE:[2008-01-01 TO 2010-12-31]
32042
32050
  - Recent failures: FAILDATE:[2020-01-01 TO *]
32043
- - By resolution type: RESTYPE:PAYOFF or RESTYPE:MERGER
32051
+ - By resolution type: RESTYPE:PAYOFF or RESTYPE:"PURCHASE AND ASSUMPTION"
32044
32052
  - Large failures by cost: COST:[100000 TO *] (cost in $thousands)
32045
32053
  - By name: NAME:"Washington Mutual"
32046
32054
 
32055
+ Resolution types (RESTYPE):
32056
+ PAYOFF = depositors paid directly, no acquirer
32057
+ PURCHASE AND ASSUMPTION = acquirer buys assets and assumes deposits
32058
+ PAYOUT = variant of payoff with insured-deposit transfer
32059
+
32047
32060
  Key returned fields:
32048
32061
  - CERT: FDIC Certificate Number
32049
32062
  - NAME: Institution name
32050
- - CITY, STALP, STNAME: Location
32063
+ - CITY, STALP (two-letter state code), STNAME (full state name): Location
32051
32064
  - FAILDATE: Date of failure (YYYY-MM-DD)
32052
- - SAVR: Savings rate at failure
32053
- - RESTYPE: Resolution type (PAYOFF, MERGER, PURCHASE & ASSUMPTION, etc.)
32065
+ - SAVR: Savings association flag (SA) or bank (BK)
32066
+ - RESTYPE: Resolution type (see above)
32054
32067
  - QBFASSET: Total assets at failure ($thousands)
32055
- - COST: Estimated cost to FDIC DIF ($thousands)
32068
+ - COST: Estimated cost to FDIC Deposit Insurance Fund ($thousands)
32056
32069
 
32057
32070
  Args:
32058
32071
  - filters (string, optional): ElasticSearch query filter
@@ -32172,6 +32185,27 @@ var import_zod2 = require("zod");
32172
32185
  var CHUNK_SIZE = 25;
32173
32186
  var MAX_CONCURRENCY = 4;
32174
32187
  var ANALYSIS_TIMEOUT_MS = 9e4;
32188
+ function getDefaultReportDate() {
32189
+ const target = new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
32190
+ const year = target.getFullYear();
32191
+ const month = target.getMonth() + 1;
32192
+ if (month >= 10) return `${year}0930`;
32193
+ if (month >= 7) return `${year}0630`;
32194
+ if (month >= 4) return `${year}0331`;
32195
+ return `${year - 1}1231`;
32196
+ }
32197
+ function getReportDateOneYearPrior(repdte) {
32198
+ const year = Number.parseInt(repdte.slice(0, 4), 10);
32199
+ return `${year - 1}${repdte.slice(4)}`;
32200
+ }
32201
+ var VALID_QUARTER_END_SUFFIXES = /* @__PURE__ */ new Set(["0331", "0630", "0930", "1231"]);
32202
+ function validateQuarterEndDate(repdte, label) {
32203
+ const suffix = repdte.slice(4);
32204
+ if (!VALID_QUARTER_END_SUFFIXES.has(suffix)) {
32205
+ return `${label} "${repdte}" is not a valid quarter-end date. FDIC data is published quarterly \u2014 use a date ending in 0331 (Q1), 0630 (Q2), 0930 (Q3), or 1231 (Q4). Example: ${repdte.slice(0, 4)}1231 for Q4 ${repdte.slice(0, 4)}.`;
32206
+ }
32207
+ return null;
32208
+ }
32175
32209
  function asNumber(value) {
32176
32210
  return typeof value === "number" ? value : null;
32177
32211
  }
@@ -32240,23 +32274,34 @@ Returns branch/office data including address, city, state, coordinates, branch t
32240
32274
 
32241
32275
  Common filter examples:
32242
32276
  - All branches of a bank: CERT:3511
32243
- - By state: STALP:TX
32277
+ - By state: STALP:TX (two-letter state code)
32244
32278
  - By city: CITY:"Austin"
32245
32279
  - Main offices only: BRNUM:0
32246
32280
  - By county: COUNTY:"Travis"
32247
- - Active branches: ENDEFYMD:[9999-01-01 TO *]
32248
- - By CBSA (metro area): CBSA_METRO_NAME:"New York-Newark-Jersey City"
32281
+ - Active branches only: ENDEFYMD:[9999-01-01 TO *] (sentinel date 9999-12-31 means still open)
32282
+ - By metro area (CBSA): CBSA_METRO_NAME:"New York-Newark-Jersey City"
32283
+
32284
+ Branch service types (BRSERTYP):
32285
+ 11 = Full service brick and mortar
32286
+ 12 = Full service retail
32287
+ 21 = Limited service administrative
32288
+ 22 = Limited service military
32289
+ 23 = Limited service drive-through
32290
+ 24 = Limited service loan production
32291
+ 25 = Limited service consumer/trust
32292
+ 26 = Limited service Internet/mobile
32293
+ 29 = Limited service other
32249
32294
 
32250
32295
  Key returned fields:
32251
32296
  - CERT: FDIC Certificate Number
32252
32297
  - UNINAME: Institution name
32253
32298
  - NAMEFULL: Full branch name
32254
- - ADDRESS, CITY, STALP, ZIP: Branch address
32299
+ - ADDRESS, CITY, STALP (two-letter state code), ZIP: Branch address
32255
32300
  - COUNTY: County name
32256
32301
  - BRNUM: Branch number (0 = main office)
32257
- - BRSERTYP: Branch service type
32302
+ - BRSERTYP: Branch service type code (see above)
32258
32303
  - LATITUDE, LONGITUDE: Geographic coordinates
32259
- - ESTYMD: Branch established date
32304
+ - ESTYMD: Branch established date (YYYY-MM-DD)
32260
32305
  - ENDEFYMD: Branch end date (9999-12-31 if still active)
32261
32306
 
32262
32307
  Args:
@@ -32337,22 +32382,36 @@ Common filter examples:
32337
32382
  - History for a specific bank: CERT:3511
32338
32383
  - Mergers: TYPE:merger
32339
32384
  - Failures: TYPE:failure
32340
- - Name changes: CHANGECODE:CO (name change code)
32385
+ - Name changes: CHANGECODE:CO
32341
32386
  - By date range: PROCDATE:[2008-01-01 TO 2009-12-31]
32342
- - By state: PSTALP:CA
32387
+ - By state: PSTALP:CA (two-letter state code)
32388
+
32389
+ Event types (TYPE):
32390
+ merger = institution was merged into another
32391
+ failure = institution failed
32392
+ assistance = received FDIC assistance transaction
32393
+ insurance = insurance-related event (new coverage, termination)
32394
+
32395
+ Common change codes (CHANGECODE):
32396
+ CO = name change
32397
+ CR = charter conversion
32398
+ DC = deposit assumption change
32399
+ MA = merger/acquisition (absorbed by another institution)
32400
+ NI = new institution insured
32401
+ TC = trust company conversion
32343
32402
 
32344
32403
  Key returned fields:
32345
32404
  - CERT: FDIC Certificate Number
32346
32405
  - INSTNAME: Institution name
32347
32406
  - CLASS: Charter class at time of change
32348
- - PCITY, PSTALP: Location (city, state abbreviation)
32349
- - PROCDATE: Processing date of the change
32350
- - EFFDATE: Effective date of the change
32407
+ - PCITY, PSTALP: Location (city, two-letter state code)
32408
+ - PROCDATE: Processing date of the change (YYYY-MM-DD)
32409
+ - EFFDATE: Effective date of the change (YYYY-MM-DD)
32351
32410
  - ENDEFYMD: End effective date
32352
32411
  - PCERT: Predecessor/successor CERT (for mergers)
32353
- - TYPE: Type of structural change
32354
- - CHANGECODE: Code for type of change
32355
- - CHANGECODE_DESC: Description of change code
32412
+ - TYPE: Type of structural change (see above)
32413
+ - CHANGECODE: Code for type of change (see above)
32414
+ - CHANGECODE_DESC: Human-readable description of the change code
32356
32415
  - INSDATE: Insurance date
32357
32416
 
32358
32417
  Args:
@@ -32423,7 +32482,7 @@ var FinancialQuerySchema = CommonQuerySchema.extend({
32423
32482
  "Filter by FDIC Certificate Number to get financials for a specific institution"
32424
32483
  ),
32425
32484
  repdte: import_zod4.z.string().optional().describe(
32426
- "Filter by report date in YYYYMMDD format (quarterly call report dates). Example: 20231231 for Q4 2023"
32485
+ "Filter by Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on call report dates: March 31, June 30, September 30, and December 31. Example: 20231231 for Q4 2023. If omitted, returns all available dates (sorted most recent first by default)."
32427
32486
  )
32428
32487
  });
32429
32488
  var SummaryQuerySchema = CommonQuerySchema.extend({
@@ -32448,7 +32507,7 @@ Common filter examples:
32448
32507
 
32449
32508
  Key returned fields:
32450
32509
  - CERT: FDIC Certificate Number
32451
- - REPDTE: Report date (YYYYMMDD)
32510
+ - REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
32452
32511
  - ASSET: Total assets ($thousands)
32453
32512
  - DEP: Total deposits ($thousands)
32454
32513
  - DEPDOM: Domestic deposits ($thousands)
@@ -32461,7 +32520,7 @@ Key returned fields:
32461
32520
 
32462
32521
  Args:
32463
32522
  - cert (number, optional): Filter by institution CERT number
32464
- - repdte (string, optional): Report date in YYYYMMDD format
32523
+ - repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
32465
32524
  - filters (string, optional): Additional ElasticSearch query filters
32466
32525
  - fields (string, optional): Comma-separated field names (the full set has 1,100+ fields)
32467
32526
  - limit (number): Records to return (default: 20)
@@ -32541,7 +32600,7 @@ Key returned fields:
32541
32600
  - ROA: Return on assets (%)
32542
32601
  - ROE: Return on equity (%)
32543
32602
  - OFFICES: Number of branch offices
32544
- - REPDTE: Report date
32603
+ - REPDTE: Report Date \u2014 the last day of the reporting period (YYYYMMDD)
32545
32604
 
32546
32605
  Args:
32547
32606
  - cert (number, optional): Filter by institution CERT number
@@ -32706,7 +32765,7 @@ var import_zod6 = require("zod");
32706
32765
  var DemographicsQuerySchema = CommonQuerySchema.extend({
32707
32766
  cert: import_zod6.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
32708
32767
  repdte: import_zod6.z.string().optional().describe(
32709
- "Filter by report date in YYYYMMDD format. Example: 20251231"
32768
+ "Filter by Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on: March 31, June 30, September 30, and December 31. Example: 20251231 for Q4 2025. If omitted, returns all available dates."
32710
32769
  )
32711
32770
  });
32712
32771
  function registerDemographicsTools(server) {
@@ -32727,7 +32786,7 @@ Common filter examples:
32727
32786
 
32728
32787
  Key returned fields:
32729
32788
  - CERT: FDIC Certificate Number
32730
- - REPDTE: Report date (YYYYMMDD)
32789
+ - REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
32731
32790
  - QTRNO: Quarter number
32732
32791
  - OFFTOT: Total offices
32733
32792
  - OFFSTATE: Offices in other states
@@ -32741,7 +32800,7 @@ Key returned fields:
32741
32800
 
32742
32801
  Args:
32743
32802
  - cert (number, optional): Filter by institution CERT number
32744
- - repdte (string, optional): Report date in YYYYMMDD format
32803
+ - repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
32745
32804
  - filters (string, optional): Additional ElasticSearch query filters
32746
32805
  - fields (string, optional): Comma-separated field names
32747
32806
  - limit (number): Records to return (default: 20)
@@ -32852,8 +32911,12 @@ var SnapshotAnalysisSchema = import_zod7.z.object({
32852
32911
  'Additional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"'
32853
32912
  ),
32854
32913
  active_only: import_zod7.z.boolean().default(true).describe("Limit the comparison set to currently active institutions."),
32855
- start_repdte: import_zod7.z.string().regex(/^\d{8}$/).describe("Starting report date in YYYYMMDD format."),
32856
- end_repdte: import_zod7.z.string().regex(/^\d{8}$/).describe("Ending report date in YYYYMMDD format."),
32914
+ start_repdte: import_zod7.z.string().regex(/^\d{8}$/).optional().describe(
32915
+ "Starting Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Example: 20210331 for Q1 2021. If omitted, defaults to the same quarter one year before end_repdte."
32916
+ ),
32917
+ end_repdte: import_zod7.z.string().regex(/^\d{8}$/).optional().describe(
32918
+ "Ending Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Must be later than start_repdte. Example: 20251231 for Q4 2025. If omitted, defaults to the most recent quarter-end date with published data (~90-day lag)."
32919
+ ),
32857
32920
  analysis_mode: AnalysisModeSchema.default("snapshot").describe(
32858
32921
  "Use snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range."
32859
32922
  ),
@@ -32862,14 +32925,23 @@ var SnapshotAnalysisSchema = import_zod7.z.object({
32862
32925
  ),
32863
32926
  limit: import_zod7.z.number().int().min(1).max(100).default(10).describe("Maximum number of ranked comparisons to return."),
32864
32927
  sort_by: SortFieldSchema.default("asset_growth").describe(
32865
- "Comparison field used to rank institutions."
32928
+ "Comparison field used to rank institutions. Valid options: asset_growth, asset_growth_pct, dep_growth, dep_growth_pct, netinc_change, netinc_change_pct, roa_change, roe_change, offices_change, assets_per_office_change, deposits_per_office_change, deposits_to_assets_change."
32866
32929
  ),
32867
32930
  sort_order: import_zod7.z.enum(["ASC", "DESC"]).default("DESC").describe("Sort direction for the ranked comparisons.")
32868
32931
  });
32932
+ function resolveSnapshotDefaults(params) {
32933
+ const end_repdte = params.end_repdte ?? getDefaultReportDate();
32934
+ const start_repdte = params.start_repdte ?? getReportDateOneYearPrior(end_repdte);
32935
+ return { ...params, start_repdte, end_repdte };
32936
+ }
32869
32937
  function validateSnapshotAnalysisParams(value) {
32870
32938
  if (!value.state && (!value.certs || value.certs.length === 0)) {
32871
32939
  return "Provide either state or certs.";
32872
32940
  }
32941
+ const startDateError = validateQuarterEndDate(value.start_repdte, "start_repdte");
32942
+ if (startDateError) return startDateError;
32943
+ const endDateError = validateQuarterEndDate(value.end_repdte, "end_repdte");
32944
+ if (endDateError) return endDateError;
32873
32945
  if (value.start_repdte >= value.end_repdte) {
32874
32946
  return "start_repdte must be earlier than end_repdte.";
32875
32947
  }
@@ -33395,12 +33467,12 @@ Good uses:
33395
33467
 
33396
33468
  Inputs:
33397
33469
  - state or certs: choose a geographic roster or provide a direct comparison set
33398
- - start_repdte, end_repdte: report dates in YYYYMMDD format
33470
+ - start_repdte, end_repdte: Report Dates (REPDTE) in YYYYMMDD format \u2014 must be quarter-end dates (0331, 0630, 0930, 1231)
33399
33471
  - analysis_mode: snapshot or timeseries
33400
33472
  - institution_filters: optional extra institution filter when building the roster
33401
33473
  - active_only: default true
33402
33474
  - include_demographics: default true, adds office-count comparisons when available
33403
- - sort_by: ranking field such as asset_growth, dep_growth_pct, roa_change, assets_per_office_change
33475
+ - sort_by: ranking field (default: asset_growth). All options: asset_growth, asset_growth_pct, dep_growth, dep_growth_pct, netinc_change, netinc_change_pct, roa_change, roe_change, offices_change, assets_per_office_change, deposits_per_office_change, deposits_to_assets_change
33404
33476
  - sort_order: ASC or DESC
33405
33477
  - limit: maximum ranked results to return
33406
33478
 
@@ -33413,19 +33485,20 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
33413
33485
  openWorldHint: true
33414
33486
  }
33415
33487
  },
33416
- async ({
33417
- state,
33418
- certs,
33419
- institution_filters,
33420
- active_only,
33421
- start_repdte,
33422
- end_repdte,
33423
- analysis_mode,
33424
- include_demographics,
33425
- limit,
33426
- sort_by,
33427
- sort_order
33428
- }, extra) => {
33488
+ async (rawParams, extra) => {
33489
+ const {
33490
+ state,
33491
+ certs,
33492
+ institution_filters,
33493
+ active_only,
33494
+ start_repdte,
33495
+ end_repdte,
33496
+ analysis_mode,
33497
+ include_demographics,
33498
+ limit,
33499
+ sort_by,
33500
+ sort_order
33501
+ } = resolveSnapshotDefaults(rawParams);
33429
33502
  const controller = new AbortController();
33430
33503
  const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
33431
33504
  const progressToken = extra._meta?.progressToken;
@@ -33645,7 +33718,7 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
33645
33718
  if (controller.signal.aborted) {
33646
33719
  return formatToolError(
33647
33720
  new Error(
33648
- `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Narrow the comparison set with certs or institution_filters and try again.`
33721
+ `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Try reducing the comparison set: use certs (max 100) instead of a state-wide roster, add institution_filters (e.g., BKCLASS:N), or shorten the date range for timeseries mode.`
33649
33722
  )
33650
33723
  );
33651
33724
  }
@@ -33806,7 +33879,9 @@ var PeerGroupInputSchema = import_zod8.z.object({
33806
33879
  cert: import_zod8.z.number().int().positive().optional().describe(
33807
33880
  "Subject institution CERT number. When provided, auto-derives peer criteria and ranks this bank against peers."
33808
33881
  ),
33809
- repdte: import_zod8.z.string().regex(/^\d{8}$/).describe("Report date in YYYYMMDD format."),
33882
+ repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
33883
+ "Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on: March 31, June 30, September 30, and December 31. Example: 20231231 for Q4 2023. If omitted, defaults to the most recent quarter-end date likely to have published data (~90-day lag)."
33884
+ ),
33810
33885
  asset_min: import_zod8.z.number().positive().optional().describe(
33811
33886
  "Minimum total assets ($thousands) for peer selection. Defaults to 50% of subject's report-date assets when cert is provided."
33812
33887
  ),
@@ -33962,7 +34037,8 @@ Override precedence: cert derives defaults, then explicit params override them.`
33962
34037
  openWorldHint: true
33963
34038
  }
33964
34039
  },
33965
- async (params, extra) => {
34040
+ async (rawParams, extra) => {
34041
+ const params = { ...rawParams, repdte: rawParams.repdte ?? getDefaultReportDate() };
33966
34042
  const controller = new AbortController();
33967
34043
  const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
33968
34044
  const progressToken = extra._meta?.progressToken;
@@ -33971,6 +34047,10 @@ Override precedence: cert derives defaults, then explicit params override them.`
33971
34047
  if (validationError) {
33972
34048
  return formatToolError(new Error(validationError));
33973
34049
  }
34050
+ const dateError = validateQuarterEndDate(params.repdte, "repdte");
34051
+ if (dateError) {
34052
+ return formatToolError(new Error(dateError));
34053
+ }
33974
34054
  const extraFieldsError = validateExtraFields(params.extra_fields);
33975
34055
  if (extraFieldsError) {
33976
34056
  return formatToolError(extraFieldsError);
@@ -34018,7 +34098,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
34018
34098
  if (financialRecords.length === 0) {
34019
34099
  return formatToolError(
34020
34100
  new Error(
34021
- `No financial data for CERT ${params.cert} at report date ${params.repdte}. Auto-derivation of peer criteria requires asset data at the specified report date.`
34101
+ `No financial data for CERT ${params.cert} at report date ${params.repdte}. FDIC quarterly data is published ~90 days after each quarter-end (March 31, June 30, September 30, December 31). Try an earlier quarter-end date, or verify the institution was active at that date.`
34022
34102
  )
34023
34103
  );
34024
34104
  }
@@ -34273,7 +34353,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
34273
34353
  if (controller.signal.aborted) {
34274
34354
  return formatToolError(
34275
34355
  new Error(
34276
- `Peer group analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Narrow the peer group criteria and try again.`
34356
+ `Peer group analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1e3)} seconds. Try narrowing the peer group: add a state filter, tighten the asset_min/asset_max range, or specify charter_classes.`
34277
34357
  )
34278
34358
  );
34279
34359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdic-mcp-server",
3
- "version": "1.7.6",
3
+ "version": "1.8.1",
4
4
  "description": "MCP server for the FDIC BankFind Suite API",
5
5
  "mcpName": "io.github.jflamb/fdic-mcp-server",
6
6
  "main": "dist/server.js",