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/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/index.js +1000 -0
- package/dist/server.js +1012 -0
- package/package.json +62 -0
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
|
+
});
|