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