fdic-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1000 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/index.ts
26
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
27
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
28
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
29
+ var import_express = __toESM(require("express"));
30
+
31
+ // src/constants.ts
32
+ var VERSION = "1.0.0";
33
+ var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
34
+ var CHARACTER_LIMIT = 5e4;
35
+ var ENDPOINTS = {
36
+ INSTITUTIONS: "institutions",
37
+ FAILURES: "failures",
38
+ LOCATIONS: "locations",
39
+ HISTORY: "history",
40
+ SUMMARY: "summary",
41
+ FINANCIALS: "financials",
42
+ SOD: "sod",
43
+ DEMOGRAPHICS: "demographics"
44
+ };
45
+
46
+ // src/services/fdicClient.ts
47
+ var import_axios = __toESM(require("axios"));
48
+ var apiClient = import_axios.default.create({
49
+ baseURL: FDIC_API_BASE_URL,
50
+ timeout: 3e4,
51
+ headers: {
52
+ Accept: "application/json",
53
+ "User-Agent": `fdic-mcp-server/${VERSION}`
54
+ }
55
+ });
56
+ async function queryEndpoint(endpoint, params) {
57
+ try {
58
+ const queryParams = {
59
+ limit: params.limit ?? 20,
60
+ offset: params.offset ?? 0,
61
+ output: "json"
62
+ };
63
+ if (params.filters) queryParams.filters = params.filters;
64
+ if (params.fields) queryParams.fields = params.fields;
65
+ if (params.sort_by) queryParams.sort_by = params.sort_by;
66
+ if (params.sort_order) queryParams.sort_order = params.sort_order;
67
+ const response = await apiClient.get(`/${endpoint}`, {
68
+ params: queryParams
69
+ });
70
+ return response.data;
71
+ } catch (err) {
72
+ if (err instanceof import_axios.AxiosError) {
73
+ const status = err.response?.status;
74
+ const detail = err.response?.data?.message ?? err.message;
75
+ if (status === 400) {
76
+ throw new Error(
77
+ `Bad request to FDIC API: ${detail}. Check your filter syntax (use ElasticSearch query string syntax, e.g. STNAME:"California" AND ACTIVE:1).`
78
+ );
79
+ } else if (status === 429) {
80
+ throw new Error(
81
+ "FDIC API rate limit exceeded. Please wait a moment and try again."
82
+ );
83
+ } else if (status === 500) {
84
+ throw new Error(
85
+ "FDIC API server error. The service may be temporarily unavailable. Try again later."
86
+ );
87
+ } else {
88
+ throw new Error(`FDIC API error (HTTP ${status}): ${detail}`);
89
+ }
90
+ }
91
+ throw new Error(`Unexpected error calling FDIC API: ${String(err)}`);
92
+ }
93
+ }
94
+ function extractRecords(response) {
95
+ return response.data.map((item) => item.data);
96
+ }
97
+ function buildPaginationInfo(total, offset, count) {
98
+ const has_more = total > offset + count;
99
+ return {
100
+ total,
101
+ offset,
102
+ count,
103
+ has_more,
104
+ ...has_more ? { next_offset: offset + count } : {}
105
+ };
106
+ }
107
+ function truncateIfNeeded(text, charLimit) {
108
+ if (text.length <= charLimit) return text;
109
+ return text.slice(0, charLimit) + `
110
+
111
+ [Response truncated at ${charLimit} characters. Use limit/offset parameters to paginate or narrow your query with filters.]`;
112
+ }
113
+ function formatToolError(err) {
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ return {
116
+ content: [{ type: "text", text: `Error: ${message}` }],
117
+ isError: true
118
+ };
119
+ }
120
+
121
+ // src/schemas/common.ts
122
+ var import_zod = require("zod");
123
+ var CommonQuerySchema = import_zod.z.object({
124
+ filters: import_zod.z.string().optional().describe(
125
+ 'ElasticSearch query string filter. Examples: STNAME:"California", ACTIVE:1 AND ASSET:[1000000 TO *], NAME:"Chase"'
126
+ ),
127
+ fields: import_zod.z.string().optional().describe(
128
+ "Comma-separated list of fields to return. Leave empty to return all fields. Example: NAME,CERT,ASSET,DEP,STALP"
129
+ ),
130
+ limit: import_zod.z.number().int().min(1).max(1e4).default(20).describe("Maximum number of records to return (1-10000, default: 20)"),
131
+ offset: import_zod.z.number().int().min(0).default(0).describe("Number of records to skip for pagination (default: 0)"),
132
+ sort_by: import_zod.z.string().optional().describe(
133
+ "Field name to sort results by. Example: ASSET, NAME, FAILDATE"
134
+ ),
135
+ sort_order: import_zod.z.enum(["ASC", "DESC"]).default("ASC").describe("Sort direction: ASC (ascending) or DESC (descending)")
136
+ });
137
+ var CertSchema = import_zod.z.object({
138
+ cert: import_zod.z.number().int().positive().describe(
139
+ "FDIC Certificate Number \u2014 the unique identifier for an institution"
140
+ ),
141
+ fields: import_zod.z.string().optional().describe("Comma-separated list of fields to return")
142
+ });
143
+
144
+ // src/tools/institutions.ts
145
+ function registerInstitutionTools(server) {
146
+ server.registerTool(
147
+ "fdic_search_institutions",
148
+ {
149
+ title: "Search FDIC Institutions",
150
+ description: `Search for FDIC-insured financial institutions (banks and savings institutions) using flexible filters.
151
+
152
+ Returns institution profile data including name, location, charter class, asset size, deposit totals, profitability metrics, and regulatory status.
153
+
154
+ Common filter examples:
155
+ - By state: STNAME:"California"
156
+ - Active banks only: ACTIVE:1
157
+ - Large banks: ASSET:[10000000 TO *] (assets in $thousands)
158
+ - By bank class: BKCLASS:N (national bank), BKCLASS:SM (state member bank), BKCLASS:NM (state non-member)
159
+ - By name: NAME:"Wells Fargo"
160
+ - Commercial banks: CB:1
161
+ - Savings institutions: MUTUAL:1
162
+ - Recently established: ESTYMD:[2010-01-01 TO *]
163
+
164
+ Key returned fields:
165
+ - CERT: FDIC Certificate Number (unique ID)
166
+ - NAME: Institution name
167
+ - CITY, STALP, STNAME: Location
168
+ - ASSET: Total assets ($thousands)
169
+ - DEP: Total deposits ($thousands)
170
+ - BKCLASS: Charter class code
171
+ - ACTIVE: 1 if currently active, 0 if inactive
172
+ - ROA, ROE: Profitability ratios
173
+ - OFFICES: Number of branch offices
174
+ - ESTYMD: Establishment date
175
+ - REGAGNT: Primary federal regulator
176
+
177
+ Args:
178
+ - filters (string, optional): ElasticSearch query filter
179
+ - fields (string, optional): Comma-separated field names
180
+ - limit (number): Records to return, 1-10000 (default: 20)
181
+ - offset (number): Pagination offset (default: 0)
182
+ - sort_by (string, optional): Field to sort by
183
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
184
+
185
+ Returns JSON with { total, offset, count, has_more, next_offset?, institutions[] }`,
186
+ inputSchema: CommonQuerySchema,
187
+ annotations: {
188
+ readOnlyHint: true,
189
+ destructiveHint: false,
190
+ idempotentHint: true,
191
+ openWorldHint: true
192
+ }
193
+ },
194
+ async (params) => {
195
+ try {
196
+ const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, params);
197
+ const records = extractRecords(response);
198
+ const pagination = buildPaginationInfo(
199
+ response.meta.total,
200
+ params.offset ?? 0,
201
+ records.length
202
+ );
203
+ const output = { ...pagination, institutions: records };
204
+ const text = truncateIfNeeded(
205
+ JSON.stringify(output, null, 2),
206
+ CHARACTER_LIMIT
207
+ );
208
+ return {
209
+ content: [{ type: "text", text }],
210
+ structuredContent: output
211
+ };
212
+ } catch (err) {
213
+ return formatToolError(err);
214
+ }
215
+ }
216
+ );
217
+ server.registerTool(
218
+ "fdic_get_institution",
219
+ {
220
+ title: "Get Institution by Certificate Number",
221
+ description: `Retrieve detailed information for a specific FDIC-insured institution using its FDIC Certificate Number (CERT).
222
+
223
+ Use this when you know the exact CERT number for an institution. To find a CERT number, use fdic_search_institutions first.
224
+
225
+ Args:
226
+ - cert (number): FDIC Certificate Number (e.g., 3511 for Bank of America)
227
+ - fields (string, optional): Comma-separated list of fields to return
228
+
229
+ Returns full institution profile including financial metrics, charter info, locations, and regulatory details.`,
230
+ inputSchema: CertSchema,
231
+ annotations: {
232
+ readOnlyHint: true,
233
+ destructiveHint: false,
234
+ idempotentHint: true,
235
+ openWorldHint: false
236
+ }
237
+ },
238
+ async ({ cert, fields }) => {
239
+ try {
240
+ const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, {
241
+ filters: `CERT:${cert}`,
242
+ fields,
243
+ limit: 1
244
+ });
245
+ const records = extractRecords(response);
246
+ if (records.length === 0) {
247
+ const output2 = {
248
+ found: false,
249
+ cert,
250
+ message: `No institution found with CERT number ${cert}.`
251
+ };
252
+ return {
253
+ content: [{ type: "text", text: output2.message }],
254
+ structuredContent: output2
255
+ };
256
+ }
257
+ const output = records[0];
258
+ const text = JSON.stringify(output, null, 2);
259
+ return {
260
+ content: [{ type: "text", text }],
261
+ structuredContent: output
262
+ };
263
+ } catch (err) {
264
+ return formatToolError(err);
265
+ }
266
+ }
267
+ );
268
+ }
269
+
270
+ // src/tools/failures.ts
271
+ function registerFailureTools(server) {
272
+ server.registerTool(
273
+ "fdic_search_failures",
274
+ {
275
+ title: "Search Bank Failures",
276
+ description: `Search for details on failed FDIC-insured financial institutions.
277
+
278
+ Returns data on bank failures including failure date, resolution type, estimated cost to the FDIC Deposit Insurance Fund, and acquiring institution info.
279
+
280
+ Common filter examples:
281
+ - By state: STALP:CA
282
+ - By year range: FAILDATE:[2008-01-01 TO 2010-12-31]
283
+ - Recent failures: FAILDATE:[2020-01-01 TO *]
284
+ - By resolution type: RESTYPE:PAYOFF or RESTYPE:MERGER
285
+ - Large failures by cost: COST:[100000 TO *] (cost in $thousands)
286
+ - By name: NAME:"Washington Mutual"
287
+
288
+ Key returned fields:
289
+ - CERT: FDIC Certificate Number
290
+ - NAME: Institution name
291
+ - CITY, STALP, STNAME: Location
292
+ - FAILDATE: Date of failure (YYYY-MM-DD)
293
+ - SAVR: Savings rate at failure
294
+ - RESTYPE: Resolution type (PAYOFF, MERGER, PURCHASE & ASSUMPTION, etc.)
295
+ - QBFASSET: Total assets at failure ($thousands)
296
+ - COST: Estimated cost to FDIC DIF ($thousands)
297
+
298
+ Args:
299
+ - filters (string, optional): ElasticSearch query filter
300
+ - fields (string, optional): Comma-separated field names
301
+ - limit (number): Records to return (default: 20)
302
+ - offset (number): Pagination offset (default: 0)
303
+ - sort_by (string, optional): Field to sort by (e.g., FAILDATE, COST)
304
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
305
+
306
+ Returns JSON with { total, offset, count, has_more, next_offset?, failures[] }`,
307
+ inputSchema: CommonQuerySchema,
308
+ annotations: {
309
+ readOnlyHint: true,
310
+ destructiveHint: false,
311
+ idempotentHint: true,
312
+ openWorldHint: true
313
+ }
314
+ },
315
+ async (params) => {
316
+ try {
317
+ const response = await queryEndpoint(ENDPOINTS.FAILURES, params);
318
+ const records = extractRecords(response);
319
+ const pagination = buildPaginationInfo(
320
+ response.meta.total,
321
+ params.offset ?? 0,
322
+ records.length
323
+ );
324
+ const output = { ...pagination, failures: records };
325
+ const text = truncateIfNeeded(
326
+ JSON.stringify(output, null, 2),
327
+ CHARACTER_LIMIT
328
+ );
329
+ return {
330
+ content: [{ type: "text", text }],
331
+ structuredContent: output
332
+ };
333
+ } catch (err) {
334
+ return formatToolError(err);
335
+ }
336
+ }
337
+ );
338
+ server.registerTool(
339
+ "fdic_get_institution_failure",
340
+ {
341
+ title: "Get Failure Details by Certificate Number",
342
+ description: `Retrieve failure details for a specific institution by FDIC Certificate Number.
343
+
344
+ Use this when you know the CERT of a failed institution to get its specific failure record.
345
+
346
+ Args:
347
+ - cert (number): FDIC Certificate Number of the failed institution
348
+ - fields (string, optional): Comma-separated list of fields to return
349
+
350
+ Returns failure details including failure date, resolution method, and cost to FDIC.`,
351
+ inputSchema: CertSchema,
352
+ annotations: {
353
+ readOnlyHint: true,
354
+ destructiveHint: false,
355
+ idempotentHint: true,
356
+ openWorldHint: false
357
+ }
358
+ },
359
+ async ({ cert, fields }) => {
360
+ try {
361
+ const response = await queryEndpoint(ENDPOINTS.FAILURES, {
362
+ filters: `CERT:${cert}`,
363
+ fields,
364
+ limit: 1
365
+ });
366
+ const records = extractRecords(response);
367
+ if (records.length === 0) {
368
+ const output2 = {
369
+ found: false,
370
+ cert,
371
+ message: `No failure record found for CERT ${cert}. The institution may not have failed, or the CERT may be incorrect.`
372
+ };
373
+ return {
374
+ content: [{ type: "text", text: output2.message }],
375
+ structuredContent: output2
376
+ };
377
+ }
378
+ const output = records[0];
379
+ const text = JSON.stringify(output, null, 2);
380
+ return {
381
+ content: [{ type: "text", text }],
382
+ structuredContent: output
383
+ };
384
+ } catch (err) {
385
+ return formatToolError(err);
386
+ }
387
+ }
388
+ );
389
+ }
390
+
391
+ // src/tools/locations.ts
392
+ var import_zod2 = require("zod");
393
+ var LocationQuerySchema = CommonQuerySchema.extend({
394
+ cert: import_zod2.z.number().int().positive().optional().describe(
395
+ "Filter by FDIC Certificate Number to get all branches of a specific institution"
396
+ )
397
+ });
398
+ function registerLocationTools(server) {
399
+ server.registerTool(
400
+ "fdic_search_locations",
401
+ {
402
+ title: "Search Institution Locations / Branches",
403
+ description: `Search for branch locations of FDIC-insured financial institutions.
404
+
405
+ Returns branch/office data including address, city, state, coordinates, branch type, and establishment date.
406
+
407
+ Common filter examples:
408
+ - All branches of a bank: CERT:3511
409
+ - By state: STALP:TX
410
+ - By city: CITY:"Austin"
411
+ - Main offices only: BRNUM:0
412
+ - By county: COUNTY:"Travis"
413
+ - Active branches: ENDEFYMD:[9999-01-01 TO *]
414
+ - By CBSA (metro area): CBSA_METRO_NAME:"New York-Newark-Jersey City"
415
+
416
+ Key returned fields:
417
+ - CERT: FDIC Certificate Number
418
+ - UNINAME: Institution name
419
+ - NAMEFULL: Full branch name
420
+ - ADDRESS, CITY, STALP, ZIP: Branch address
421
+ - COUNTY: County name
422
+ - BRNUM: Branch number (0 = main office)
423
+ - BRSERTYP: Branch service type
424
+ - LATITUDE, LONGITUDE: Geographic coordinates
425
+ - ESTYMD: Branch established date
426
+ - ENDEFYMD: Branch end date (9999-12-31 if still active)
427
+
428
+ Args:
429
+ - cert (number, optional): Filter by institution CERT number
430
+ - filters (string, optional): Additional ElasticSearch query filters
431
+ - fields (string, optional): Comma-separated field names
432
+ - limit (number): Records to return (default: 20)
433
+ - offset (number): Pagination offset (default: 0)
434
+ - sort_by (string, optional): Field to sort by
435
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
436
+
437
+ Returns JSON with { total, offset, count, has_more, next_offset?, locations[] }`,
438
+ inputSchema: LocationQuerySchema,
439
+ annotations: {
440
+ readOnlyHint: true,
441
+ destructiveHint: false,
442
+ idempotentHint: true,
443
+ openWorldHint: true
444
+ }
445
+ },
446
+ async ({ cert, ...params }) => {
447
+ try {
448
+ let filters = params.filters ?? "";
449
+ if (cert !== void 0) {
450
+ filters = filters ? `CERT:${cert} AND (${filters})` : `CERT:${cert}`;
451
+ }
452
+ const response = await queryEndpoint(ENDPOINTS.LOCATIONS, {
453
+ ...params,
454
+ filters: filters || void 0
455
+ });
456
+ const records = extractRecords(response);
457
+ const pagination = buildPaginationInfo(
458
+ response.meta.total,
459
+ params.offset ?? 0,
460
+ records.length
461
+ );
462
+ const output = { ...pagination, locations: records };
463
+ const text = truncateIfNeeded(
464
+ JSON.stringify(output, null, 2),
465
+ CHARACTER_LIMIT
466
+ );
467
+ return {
468
+ content: [{ type: "text", text }],
469
+ structuredContent: output
470
+ };
471
+ } catch (err) {
472
+ return formatToolError(err);
473
+ }
474
+ }
475
+ );
476
+ }
477
+
478
+ // src/tools/history.ts
479
+ var import_zod3 = require("zod");
480
+ var HistoryQuerySchema = CommonQuerySchema.extend({
481
+ cert: import_zod3.z.number().int().positive().optional().describe(
482
+ "Filter by FDIC Certificate Number to get history for a specific institution"
483
+ )
484
+ });
485
+ function registerHistoryTools(server) {
486
+ server.registerTool(
487
+ "fdic_search_history",
488
+ {
489
+ title: "Search Institution History / Structure Changes",
490
+ description: `Search for structural change events for FDIC-insured financial institutions.
491
+
492
+ Returns records on mergers, acquisitions, name changes, charter conversions, failures, and other significant structural events.
493
+
494
+ Common filter examples:
495
+ - History for a specific bank: CERT:3511
496
+ - Mergers: TYPE:merger
497
+ - Failures: TYPE:failure
498
+ - Name changes: CHANGECODE:CO (name change code)
499
+ - By date range: PROCDATE:[2008-01-01 TO 2009-12-31]
500
+ - By state: PSTALP:CA
501
+
502
+ Key returned fields:
503
+ - CERT: FDIC Certificate Number
504
+ - INSTNAME: Institution name
505
+ - CLASS: Charter class at time of change
506
+ - PCITY, PSTALP: Location (city, state abbreviation)
507
+ - PROCDATE: Processing date of the change
508
+ - EFFDATE: Effective date of the change
509
+ - ENDEFYMD: End effective date
510
+ - PCERT: Predecessor/successor CERT (for mergers)
511
+ - TYPE: Type of structural change
512
+ - CHANGECODE: Code for type of change
513
+ - CHANGECODE_DESC: Description of change code
514
+ - INSDATE: Insurance date
515
+
516
+ Args:
517
+ - cert (number, optional): Filter by institution CERT number
518
+ - filters (string, optional): ElasticSearch query filters
519
+ - fields (string, optional): Comma-separated field names
520
+ - limit (number): Records to return (default: 20)
521
+ - offset (number): Pagination offset (default: 0)
522
+ - sort_by (string, optional): Field to sort by (e.g., PROCDATE)
523
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
524
+
525
+ Returns JSON with { total, offset, count, has_more, next_offset?, events[] }`,
526
+ inputSchema: HistoryQuerySchema,
527
+ annotations: {
528
+ readOnlyHint: true,
529
+ destructiveHint: false,
530
+ idempotentHint: true,
531
+ openWorldHint: true
532
+ }
533
+ },
534
+ async ({ cert, ...params }) => {
535
+ try {
536
+ let filters = params.filters ?? "";
537
+ if (cert !== void 0) {
538
+ filters = filters ? `CERT:${cert} AND (${filters})` : `CERT:${cert}`;
539
+ }
540
+ const response = await queryEndpoint(ENDPOINTS.HISTORY, {
541
+ ...params,
542
+ filters: filters || void 0
543
+ });
544
+ const records = extractRecords(response);
545
+ const pagination = buildPaginationInfo(
546
+ response.meta.total,
547
+ params.offset ?? 0,
548
+ records.length
549
+ );
550
+ const output = { ...pagination, events: records };
551
+ const text = truncateIfNeeded(
552
+ JSON.stringify(output, null, 2),
553
+ CHARACTER_LIMIT
554
+ );
555
+ return {
556
+ content: [{ type: "text", text }],
557
+ structuredContent: output
558
+ };
559
+ } catch (err) {
560
+ return formatToolError(err);
561
+ }
562
+ }
563
+ );
564
+ }
565
+
566
+ // src/tools/financials.ts
567
+ var import_zod4 = require("zod");
568
+ var FinancialQuerySchema = CommonQuerySchema.extend({
569
+ sort_order: import_zod4.z.enum(["ASC", "DESC"]).default("DESC").describe(
570
+ "Sort direction: DESC (descending, default for most recent first) or ASC (ascending)"
571
+ ),
572
+ cert: import_zod4.z.number().int().positive().optional().describe(
573
+ "Filter by FDIC Certificate Number to get financials for a specific institution"
574
+ ),
575
+ repdte: import_zod4.z.string().optional().describe(
576
+ "Filter by report date in YYYYMMDD format (quarterly call report dates). Example: 20231231 for Q4 2023"
577
+ )
578
+ });
579
+ var SummaryQuerySchema = CommonQuerySchema.extend({
580
+ cert: import_zod4.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
581
+ year: import_zod4.z.number().int().min(1934).optional().describe("Filter by specific year (e.g., 2022)")
582
+ });
583
+ function registerFinancialTools(server) {
584
+ server.registerTool(
585
+ "fdic_search_financials",
586
+ {
587
+ title: "Search Institution Financial Data",
588
+ description: `Search quarterly financial (Call Report) data for FDIC-insured institutions. Covers over 1,100 financial variables reported quarterly.
589
+
590
+ Returns balance sheet, income statement, capital, and performance ratio data from FDIC Call Reports.
591
+
592
+ Common filter examples:
593
+ - Financials for a specific bank: CERT:3511
594
+ - By report date: REPDTE:20231231
595
+ - High-profit banks in Q4 2023: REPDTE:20231231 AND ROA:[1.5 TO *]
596
+ - Large banks most recent: ASSET:[10000000 TO *]
597
+ - Negative net income: NETINC:[* TO 0]
598
+
599
+ Key returned fields:
600
+ - CERT: FDIC Certificate Number
601
+ - REPDTE: Report date (YYYYMMDD)
602
+ - ASSET: Total assets ($thousands)
603
+ - DEP: Total deposits ($thousands)
604
+ - DEPDOM: Domestic deposits ($thousands)
605
+ - INTINC: Total interest income ($thousands)
606
+ - EINTEXP: Total interest expense ($thousands)
607
+ - NETINC: Net income ($thousands)
608
+ - ROA: Return on assets (%)
609
+ - ROE: Return on equity (%)
610
+ - NETNIM: Net interest margin (%)
611
+
612
+ Args:
613
+ - cert (number, optional): Filter by institution CERT number
614
+ - repdte (string, optional): Report date in YYYYMMDD format
615
+ - filters (string, optional): Additional ElasticSearch query filters
616
+ - fields (string, optional): Comma-separated field names (the full set has 1,100+ fields)
617
+ - limit (number): Records to return (default: 20)
618
+ - offset (number): Pagination offset (default: 0)
619
+ - sort_by (string, optional): Field to sort by
620
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'DESC' recommended for most recent first)
621
+
622
+ Returns JSON with { total, offset, count, has_more, next_offset?, financials[] }`,
623
+ inputSchema: FinancialQuerySchema,
624
+ annotations: {
625
+ readOnlyHint: true,
626
+ destructiveHint: false,
627
+ idempotentHint: true,
628
+ openWorldHint: true
629
+ }
630
+ },
631
+ async ({ cert, repdte, ...params }) => {
632
+ try {
633
+ const filterParts = [];
634
+ if (params.filters) filterParts.push(`(${params.filters})`);
635
+ if (cert !== void 0) filterParts.push(`CERT:${cert}`);
636
+ if (repdte) filterParts.push(`REPDTE:${repdte}`);
637
+ const response = await queryEndpoint(ENDPOINTS.FINANCIALS, {
638
+ ...params,
639
+ filters: filterParts.length > 0 ? filterParts.join(" AND ") : void 0
640
+ });
641
+ const records = extractRecords(response);
642
+ const pagination = buildPaginationInfo(
643
+ response.meta.total,
644
+ params.offset ?? 0,
645
+ records.length
646
+ );
647
+ const output = { ...pagination, financials: records };
648
+ const text = truncateIfNeeded(
649
+ JSON.stringify(output, null, 2),
650
+ CHARACTER_LIMIT
651
+ );
652
+ return {
653
+ content: [{ type: "text", text }],
654
+ structuredContent: output
655
+ };
656
+ } catch (err) {
657
+ return formatToolError(err);
658
+ }
659
+ }
660
+ );
661
+ server.registerTool(
662
+ "fdic_search_summary",
663
+ {
664
+ title: "Search Annual Financial Summary Data",
665
+ description: `Search aggregate financial and structure summary data subtotaled by year for FDIC-insured institutions.
666
+
667
+ Returns annual snapshots of key financial metrics \u2014 useful for tracking an institution's growth over time.
668
+
669
+ Common filter examples:
670
+ - Annual history for a bank: CERT:3511
671
+ - Specific year: YEAR:2022
672
+ - Year range: YEAR:[2010 TO 2020]
673
+ - Large banks in 2022: YEAR:2022 AND ASSET:[10000000 TO *]
674
+ - Profitable in 2023: YEAR:2023 AND ROE:[10 TO *]
675
+
676
+ Key returned fields:
677
+ - CERT: FDIC Certificate Number
678
+ - YEAR: Report year
679
+ - ASSET: Total assets ($thousands)
680
+ - DEP: Total deposits ($thousands)
681
+ - NETINC: Net income ($thousands)
682
+ - ROA: Return on assets (%)
683
+ - ROE: Return on equity (%)
684
+ - OFFICES: Number of branch offices
685
+ - REPDTE: Report date
686
+
687
+ Args:
688
+ - cert (number, optional): Filter by institution CERT number
689
+ - year (number, optional): Filter by specific year (1934-present)
690
+ - filters (string, optional): Additional ElasticSearch query filters
691
+ - fields (string, optional): Comma-separated field names
692
+ - limit (number): Records to return (default: 20)
693
+ - offset (number): Pagination offset (default: 0)
694
+ - sort_by (string, optional): Field to sort by (e.g., YEAR, ASSET)
695
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
696
+
697
+ Returns JSON with { total, offset, count, has_more, next_offset?, summary[] }`,
698
+ inputSchema: SummaryQuerySchema,
699
+ annotations: {
700
+ readOnlyHint: true,
701
+ destructiveHint: false,
702
+ idempotentHint: true,
703
+ openWorldHint: true
704
+ }
705
+ },
706
+ async ({ cert, year, ...params }) => {
707
+ try {
708
+ const filterParts = [];
709
+ if (params.filters) filterParts.push(`(${params.filters})`);
710
+ if (cert !== void 0) filterParts.push(`CERT:${cert}`);
711
+ if (year !== void 0) filterParts.push(`YEAR:${year}`);
712
+ const response = await queryEndpoint(ENDPOINTS.SUMMARY, {
713
+ ...params,
714
+ filters: filterParts.length > 0 ? filterParts.join(" AND ") : void 0
715
+ });
716
+ const records = extractRecords(response);
717
+ const pagination = buildPaginationInfo(
718
+ response.meta.total,
719
+ params.offset ?? 0,
720
+ records.length
721
+ );
722
+ const output = { ...pagination, summary: records };
723
+ const text = truncateIfNeeded(
724
+ JSON.stringify(output, null, 2),
725
+ CHARACTER_LIMIT
726
+ );
727
+ return {
728
+ content: [{ type: "text", text }],
729
+ structuredContent: output
730
+ };
731
+ } catch (err) {
732
+ return formatToolError(err);
733
+ }
734
+ }
735
+ );
736
+ }
737
+
738
+ // src/tools/sod.ts
739
+ var import_zod5 = require("zod");
740
+ var SodQuerySchema = CommonQuerySchema.extend({
741
+ cert: import_zod5.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
742
+ year: import_zod5.z.number().int().min(1994).optional().describe(
743
+ "Filter by specific year (1994-present). SOD data is annual."
744
+ )
745
+ });
746
+ function registerSodTools(server) {
747
+ server.registerTool(
748
+ "fdic_search_sod",
749
+ {
750
+ title: "Search Summary of Deposits (SOD)",
751
+ description: `Search annual Summary of Deposits (SOD) data for individual bank branches.
752
+
753
+ The SOD report provides annual deposit data at the branch level, showing deposit balances for each office of every FDIC-insured institution as of June 30 each year.
754
+
755
+ Common filter examples:
756
+ - All branches for a bank: CERT:3511
757
+ - SOD for specific year: YEAR:2022
758
+ - Branches in a state: STALPBR:CA
759
+ - Branches in a city: CITYBR:"Austin"
760
+ - High-deposit branches: DEPSUMBR:[1000000 TO *]
761
+ - By metro area: MSANAMEBR:"Dallas-Fort Worth-Arlington"
762
+
763
+ Key returned fields:
764
+ - YEAR: Report year (as of June 30)
765
+ - CERT: FDIC Certificate Number
766
+ - BRNUM: Branch number (0 = main office)
767
+ - UNINAME: Institution name
768
+ - NAMEFULL: Full branch name
769
+ - ADDRESBR, CITYBR, STALPBR, ZIPBR: Branch address
770
+ - CNTYBR: County
771
+ - DEPSUMBR: Total deposits at branch ($thousands)
772
+ - MSABR: Metropolitan Statistical Area code
773
+ - MSANAMEBR: MSA name
774
+ - LATITUDE, LONGITUDE: Coordinates
775
+
776
+ Args:
777
+ - cert (number, optional): Filter by institution CERT number
778
+ - year (number, optional): SOD report year (1994-present)
779
+ - filters (string, optional): Additional ElasticSearch query filters
780
+ - fields (string, optional): Comma-separated field names
781
+ - limit (number): Records to return (default: 20)
782
+ - offset (number): Pagination offset (default: 0)
783
+ - sort_by (string, optional): Field to sort by (e.g., DEPSUMBR, YEAR)
784
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
785
+
786
+ Returns JSON with { total, offset, count, has_more, next_offset?, deposits[] }`,
787
+ inputSchema: SodQuerySchema,
788
+ annotations: {
789
+ readOnlyHint: true,
790
+ destructiveHint: false,
791
+ idempotentHint: true,
792
+ openWorldHint: true
793
+ }
794
+ },
795
+ async ({ cert, year, ...params }) => {
796
+ try {
797
+ const filterParts = [];
798
+ if (params.filters) filterParts.push(`(${params.filters})`);
799
+ if (cert !== void 0) filterParts.push(`CERT:${cert}`);
800
+ if (year !== void 0) filterParts.push(`YEAR:${year}`);
801
+ const response = await queryEndpoint(ENDPOINTS.SOD, {
802
+ ...params,
803
+ filters: filterParts.length > 0 ? filterParts.join(" AND ") : void 0
804
+ });
805
+ const records = extractRecords(response);
806
+ const pagination = buildPaginationInfo(
807
+ response.meta.total,
808
+ params.offset ?? 0,
809
+ records.length
810
+ );
811
+ const output = { ...pagination, deposits: records };
812
+ const text = truncateIfNeeded(
813
+ JSON.stringify(output, null, 2),
814
+ CHARACTER_LIMIT
815
+ );
816
+ return {
817
+ content: [{ type: "text", text }],
818
+ structuredContent: output
819
+ };
820
+ } catch (err) {
821
+ return formatToolError(err);
822
+ }
823
+ }
824
+ );
825
+ }
826
+
827
+ // src/tools/demographics.ts
828
+ var import_zod6 = require("zod");
829
+ var DemographicsQuerySchema = CommonQuerySchema.extend({
830
+ cert: import_zod6.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
831
+ repdte: import_zod6.z.string().optional().describe(
832
+ "Filter by report date in YYYYMMDD format. Example: 20251231"
833
+ )
834
+ });
835
+ function registerDemographicsTools(server) {
836
+ server.registerTool(
837
+ "fdic_search_demographics",
838
+ {
839
+ title: "Search Institution Demographics Data",
840
+ description: `Search BankFind demographics data for FDIC-insured institutions.
841
+
842
+ Returns quarterly demographic and market-structure attributes such as office counts, territory assignments, metro classification, county/country codes, and selected geographic reference data.
843
+
844
+ Common filter examples:
845
+ - Demographics for a specific bank: CERT:3511
846
+ - By report date: REPDTE:20251231
847
+ - Institutions in metro areas: METRO:1
848
+ - Institutions with out-of-state offices: OFFSTATE:[1 TO *]
849
+ - Minority status date present: MNRTYDTE:[19000101 TO 99991231]
850
+
851
+ Key returned fields:
852
+ - CERT: FDIC Certificate Number
853
+ - REPDTE: Report date (YYYYMMDD)
854
+ - QTRNO: Quarter number
855
+ - OFFTOT: Total offices
856
+ - OFFSTATE: Offices in other states
857
+ - OFFNDOM: Offices in non-domestic territories
858
+ - OFFOTH: Other offices
859
+ - OFFSOD: Offices included in Summary of Deposits
860
+ - METRO, MICRO: Metro/micro area flags
861
+ - CBSANAME, CSA: Core-based statistical area data
862
+ - FDICTERR, RISKTERR: FDIC and risk territory assignments
863
+ - SIMS_LAT, SIMS_LONG: Geographic coordinates
864
+
865
+ Args:
866
+ - cert (number, optional): Filter by institution CERT number
867
+ - repdte (string, optional): Report date in YYYYMMDD format
868
+ - filters (string, optional): Additional ElasticSearch query filters
869
+ - fields (string, optional): Comma-separated field names
870
+ - limit (number): Records to return (default: 20)
871
+ - offset (number): Pagination offset (default: 0)
872
+ - sort_by (string, optional): Field to sort by
873
+ - sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
874
+
875
+ Returns JSON with { total, offset, count, has_more, next_offset?, demographics[] }`,
876
+ inputSchema: DemographicsQuerySchema,
877
+ annotations: {
878
+ readOnlyHint: true,
879
+ destructiveHint: false,
880
+ idempotentHint: true,
881
+ openWorldHint: true
882
+ }
883
+ },
884
+ async ({ cert, repdte, ...params }) => {
885
+ try {
886
+ const filterParts = [];
887
+ if (params.filters) filterParts.push(`(${params.filters})`);
888
+ if (cert !== void 0) filterParts.push(`CERT:${cert}`);
889
+ if (repdte) filterParts.push(`REPDTE:${repdte}`);
890
+ const response = await queryEndpoint(ENDPOINTS.DEMOGRAPHICS, {
891
+ ...params,
892
+ filters: filterParts.length > 0 ? filterParts.join(" AND ") : void 0
893
+ });
894
+ const records = extractRecords(response);
895
+ const pagination = buildPaginationInfo(
896
+ response.meta.total,
897
+ params.offset ?? 0,
898
+ records.length
899
+ );
900
+ const output = { ...pagination, demographics: records };
901
+ const text = truncateIfNeeded(
902
+ JSON.stringify(output, null, 2),
903
+ CHARACTER_LIMIT
904
+ );
905
+ return {
906
+ content: [{ type: "text", text }],
907
+ structuredContent: output
908
+ };
909
+ } catch (err) {
910
+ return formatToolError(err);
911
+ }
912
+ }
913
+ );
914
+ }
915
+
916
+ // src/index.ts
917
+ function createServer() {
918
+ const server = new import_mcp.McpServer({
919
+ name: "fdic-mcp-server",
920
+ version: VERSION
921
+ });
922
+ registerInstitutionTools(server);
923
+ registerFailureTools(server);
924
+ registerLocationTools(server);
925
+ registerHistoryTools(server);
926
+ registerFinancialTools(server);
927
+ registerSodTools(server);
928
+ registerDemographicsTools(server);
929
+ return server;
930
+ }
931
+ async function runStdio() {
932
+ const server = createServer();
933
+ const transport = new import_stdio.StdioServerTransport();
934
+ await server.connect(transport);
935
+ console.error("FDIC BankFind MCP server running on stdio");
936
+ }
937
+ function createApp() {
938
+ const app = (0, import_express.default)();
939
+ app.use(import_express.default.json());
940
+ app.get("/health", (_req, res) => {
941
+ res.json({ status: "ok", server: "fdic-mcp-server", version: VERSION });
942
+ });
943
+ app.post("/mcp", async (req, res) => {
944
+ const server = createServer();
945
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({
946
+ sessionIdGenerator: void 0,
947
+ enableJsonResponse: true
948
+ });
949
+ res.on("close", () => {
950
+ void transport.close().catch(() => {
951
+ });
952
+ void server.close().catch(() => {
953
+ });
954
+ });
955
+ try {
956
+ await server.connect(transport);
957
+ await transport.handleRequest(req, res, req.body);
958
+ } catch (error) {
959
+ console.error("MCP request error:", error);
960
+ if (!res.headersSent) {
961
+ res.status(500).json({
962
+ jsonrpc: "2.0",
963
+ error: {
964
+ code: -32603,
965
+ message: "Internal server error"
966
+ },
967
+ id: null
968
+ });
969
+ }
970
+ await transport.close().catch(() => {
971
+ });
972
+ await server.close().catch(() => {
973
+ });
974
+ }
975
+ });
976
+ return app;
977
+ }
978
+ async function runHTTP() {
979
+ const app = createApp();
980
+ const port = parseInt(process.env.PORT ?? "3000");
981
+ app.listen(port, () => {
982
+ console.error(
983
+ `FDIC BankFind MCP server running on http://localhost:${port}/mcp`
984
+ );
985
+ });
986
+ }
987
+ async function main() {
988
+ const transportMode = process.env.TRANSPORT ?? "stdio";
989
+ if (transportMode === "http") {
990
+ await runHTTP();
991
+ } else {
992
+ await runStdio();
993
+ }
994
+ }
995
+
996
+ // src/cli.ts
997
+ main().catch((error) => {
998
+ console.error("Server error:", error);
999
+ process.exit(1);
1000
+ });