fdic-mcp-server 1.23.2 → 1.25.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/README.md +3 -0
- package/dist/index.js +1494 -474
- package/dist/server.js +1498 -476
- package/package.json +4 -2
package/dist/server.js
CHANGED
|
@@ -35,7 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
main: () => main,
|
|
36
36
|
parseAllowedOrigins: () => parseAllowedOrigins,
|
|
37
37
|
parseHttpHost: () => parseHttpHost,
|
|
38
|
-
parseHttpPort: () => parseHttpPort
|
|
38
|
+
parseHttpPort: () => parseHttpPort,
|
|
39
|
+
resolveProfile: () => resolveProfile
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
41
42
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -46,7 +47,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
46
47
|
var import_express2 = __toESM(require("express"));
|
|
47
48
|
|
|
48
49
|
// src/constants.ts
|
|
49
|
-
var VERSION = true ? "1.
|
|
50
|
+
var VERSION = true ? "1.25.0" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
50
51
|
var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
|
|
51
52
|
var CHARACTER_LIMIT = 5e4;
|
|
52
53
|
var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
@@ -31860,13 +31861,96 @@ function formatLookupResultText(label, record, preferredKeys) {
|
|
|
31860
31861
|
return `${label}
|
|
31861
31862
|
${summarizeRecord(record, preferredKeys, 8)}`;
|
|
31862
31863
|
}
|
|
31863
|
-
|
|
31864
|
+
var ERROR_CODE_FROM_MESSAGE = [
|
|
31865
|
+
{ pattern: /Bad request to FDIC API/, code: "FDIC_BAD_FILTER", retryable: false },
|
|
31866
|
+
{ pattern: /rate limit/i, code: "FDIC_RATE_LIMIT", retryable: true },
|
|
31867
|
+
{ pattern: /server error/i, code: "FDIC_UPSTREAM_ERROR", retryable: true },
|
|
31868
|
+
{
|
|
31869
|
+
pattern: /response-size limit|maxContentLength/,
|
|
31870
|
+
code: "FDIC_RESPONSE_TOO_LARGE",
|
|
31871
|
+
retryable: false
|
|
31872
|
+
},
|
|
31873
|
+
{ pattern: /canceled/i, code: "FDIC_CANCELED", retryable: true },
|
|
31874
|
+
{ pattern: /No (institution|failure|financial)/i, code: "FDIC_NOT_FOUND", retryable: false },
|
|
31875
|
+
{ pattern: /quarter-end date/i, code: "FDIC_BAD_DATE", retryable: false }
|
|
31876
|
+
];
|
|
31877
|
+
function inferErrorCode(message) {
|
|
31878
|
+
for (const entry of ERROR_CODE_FROM_MESSAGE) {
|
|
31879
|
+
if (entry.pattern.test(message)) {
|
|
31880
|
+
return { code: entry.code, retryable: entry.retryable };
|
|
31881
|
+
}
|
|
31882
|
+
}
|
|
31883
|
+
return { code: "FDIC_UNKNOWN", retryable: false };
|
|
31884
|
+
}
|
|
31885
|
+
function formatToolError(err, override) {
|
|
31864
31886
|
const message = err instanceof Error ? err.message : String(err);
|
|
31887
|
+
const inferred = inferErrorCode(message);
|
|
31888
|
+
const payload = {
|
|
31889
|
+
code: override?.code ?? inferred.code,
|
|
31890
|
+
message: override?.message ?? message,
|
|
31891
|
+
retryable: override?.retryable ?? inferred.retryable,
|
|
31892
|
+
hint: override?.hint
|
|
31893
|
+
};
|
|
31865
31894
|
return {
|
|
31866
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
31895
|
+
content: [{ type: "text", text: `Error: ${payload.message}` }],
|
|
31896
|
+
structuredContent: payload,
|
|
31867
31897
|
isError: true
|
|
31868
31898
|
};
|
|
31869
31899
|
}
|
|
31900
|
+
var DEFAULT_STRUCTURED_BYTE_LIMIT = 2e5;
|
|
31901
|
+
function capStructuredContent(output, recordKey, byteLimit = DEFAULT_STRUCTURED_BYTE_LIMIT) {
|
|
31902
|
+
const records = output[recordKey];
|
|
31903
|
+
if (!Array.isArray(records)) {
|
|
31904
|
+
return output;
|
|
31905
|
+
}
|
|
31906
|
+
const initialBytes = Buffer.byteLength(JSON.stringify(output), "utf8");
|
|
31907
|
+
if (initialBytes <= byteLimit) {
|
|
31908
|
+
return output;
|
|
31909
|
+
}
|
|
31910
|
+
let lo = 0;
|
|
31911
|
+
let hi = records.length;
|
|
31912
|
+
let best = 0;
|
|
31913
|
+
while (lo <= hi) {
|
|
31914
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
31915
|
+
const candidate = buildTruncatedPayload(output, recordKey, records, mid);
|
|
31916
|
+
const bytes = Buffer.byteLength(JSON.stringify(candidate), "utf8");
|
|
31917
|
+
if (bytes <= byteLimit) {
|
|
31918
|
+
best = mid;
|
|
31919
|
+
lo = mid + 1;
|
|
31920
|
+
} else {
|
|
31921
|
+
hi = mid - 1;
|
|
31922
|
+
}
|
|
31923
|
+
}
|
|
31924
|
+
if (best === 0) {
|
|
31925
|
+
throw new Error(
|
|
31926
|
+
"FDIC API response exceeded the configured response-size limit before parsing: a single record exceeded the structured-content byte cap. Request fewer fields with `fields=`, lower `limit`, or raise the cap."
|
|
31927
|
+
);
|
|
31928
|
+
}
|
|
31929
|
+
return buildTruncatedPayload(output, recordKey, records, best);
|
|
31930
|
+
}
|
|
31931
|
+
function buildTruncatedPayload(output, recordKey, records, slicedLength) {
|
|
31932
|
+
const sliced = records.slice(0, slicedLength);
|
|
31933
|
+
const result = {
|
|
31934
|
+
...output,
|
|
31935
|
+
[recordKey]: sliced,
|
|
31936
|
+
truncated: true
|
|
31937
|
+
};
|
|
31938
|
+
const offset = typeof output.offset === "number" ? output.offset : void 0;
|
|
31939
|
+
const upstreamCount = typeof output.count === "number" ? output.count : void 0;
|
|
31940
|
+
const upstreamNextOffset = typeof output.next_offset === "number" ? output.next_offset : void 0;
|
|
31941
|
+
if (offset !== void 0) {
|
|
31942
|
+
result.count = slicedLength;
|
|
31943
|
+
result.next_offset = offset + slicedLength;
|
|
31944
|
+
result.has_more = true;
|
|
31945
|
+
}
|
|
31946
|
+
if (upstreamCount !== void 0 || upstreamNextOffset !== void 0) {
|
|
31947
|
+
result.upstream = {
|
|
31948
|
+
...upstreamCount !== void 0 ? { count: upstreamCount } : {},
|
|
31949
|
+
...upstreamNextOffset !== void 0 ? { next_offset: upstreamNextOffset } : {}
|
|
31950
|
+
};
|
|
31951
|
+
}
|
|
31952
|
+
return result;
|
|
31953
|
+
}
|
|
31870
31954
|
|
|
31871
31955
|
// src/schemas/common.ts
|
|
31872
31956
|
var import_zod = require("zod");
|
|
@@ -31891,57 +31975,100 @@ var CertSchema = import_zod.z.object({
|
|
|
31891
31975
|
fields: import_zod.z.string().optional().describe("Comma-separated list of fields to return")
|
|
31892
31976
|
});
|
|
31893
31977
|
|
|
31978
|
+
// src/schemas/output.ts
|
|
31979
|
+
var import_zod2 = require("zod");
|
|
31980
|
+
var FdicRecord = import_zod2.z.record(import_zod2.z.unknown());
|
|
31981
|
+
var Pagination = {
|
|
31982
|
+
total: import_zod2.z.number().int(),
|
|
31983
|
+
offset: import_zod2.z.number().int(),
|
|
31984
|
+
count: import_zod2.z.number().int(),
|
|
31985
|
+
has_more: import_zod2.z.boolean(),
|
|
31986
|
+
next_offset: import_zod2.z.number().int().optional()
|
|
31987
|
+
};
|
|
31988
|
+
function paginatedSearchSchema(recordKey) {
|
|
31989
|
+
return import_zod2.z.object({
|
|
31990
|
+
...Pagination,
|
|
31991
|
+
[recordKey]: import_zod2.z.array(FdicRecord),
|
|
31992
|
+
truncated: import_zod2.z.boolean().optional()
|
|
31993
|
+
});
|
|
31994
|
+
}
|
|
31995
|
+
var FdicInstitutionsSearchOutputSchema = paginatedSearchSchema(
|
|
31996
|
+
"institutions"
|
|
31997
|
+
);
|
|
31998
|
+
var FdicFailuresSearchOutputSchema = paginatedSearchSchema("failures");
|
|
31999
|
+
var FdicLocationsSearchOutputSchema = paginatedSearchSchema("locations");
|
|
32000
|
+
var FdicHistorySearchOutputSchema = paginatedSearchSchema("events");
|
|
32001
|
+
var FdicFinancialsSearchOutputSchema = paginatedSearchSchema(
|
|
32002
|
+
"financials"
|
|
32003
|
+
);
|
|
32004
|
+
var FdicSummarySearchOutputSchema = paginatedSearchSchema("summary");
|
|
32005
|
+
var FdicSodSearchOutputSchema = paginatedSearchSchema("deposits");
|
|
32006
|
+
var FdicDemographicsSearchOutputSchema = paginatedSearchSchema(
|
|
32007
|
+
"demographics"
|
|
32008
|
+
);
|
|
32009
|
+
var FdicInstitutionLookupOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32010
|
+
var FdicFailureLookupOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32011
|
+
var ChatGptSearchResultSchema = import_zod2.z.object({
|
|
32012
|
+
results: import_zod2.z.array(
|
|
32013
|
+
import_zod2.z.object({
|
|
32014
|
+
id: import_zod2.z.string(),
|
|
32015
|
+
title: import_zod2.z.string(),
|
|
32016
|
+
url: import_zod2.z.string()
|
|
32017
|
+
})
|
|
32018
|
+
)
|
|
32019
|
+
});
|
|
32020
|
+
var ChatGptFetchResultSchema = import_zod2.z.object({
|
|
32021
|
+
id: import_zod2.z.string(),
|
|
32022
|
+
title: import_zod2.z.string(),
|
|
32023
|
+
text: import_zod2.z.string(),
|
|
32024
|
+
url: import_zod2.z.string(),
|
|
32025
|
+
metadata: import_zod2.z.record(import_zod2.z.unknown()).optional()
|
|
32026
|
+
});
|
|
32027
|
+
var Source = import_zod2.z.object({ title: import_zod2.z.string(), url: import_zod2.z.string() });
|
|
32028
|
+
var FdicBankDeepDiveOutputSchema = import_zod2.z.object({
|
|
32029
|
+
institution: import_zod2.z.object({
|
|
32030
|
+
cert: import_zod2.z.number().int(),
|
|
32031
|
+
name: import_zod2.z.string(),
|
|
32032
|
+
city: import_zod2.z.string(),
|
|
32033
|
+
state: import_zod2.z.string(),
|
|
32034
|
+
active: import_zod2.z.boolean(),
|
|
32035
|
+
asset_thousands: import_zod2.z.number().optional(),
|
|
32036
|
+
deposit_thousands: import_zod2.z.number().optional(),
|
|
32037
|
+
offices: import_zod2.z.number().optional(),
|
|
32038
|
+
charter_class: import_zod2.z.string(),
|
|
32039
|
+
regulator: import_zod2.z.string(),
|
|
32040
|
+
established: import_zod2.z.string(),
|
|
32041
|
+
report_date: import_zod2.z.string()
|
|
32042
|
+
}),
|
|
32043
|
+
assessment: import_zod2.z.object({
|
|
32044
|
+
official_rating: import_zod2.z.boolean(),
|
|
32045
|
+
proxy_band: import_zod2.z.string(),
|
|
32046
|
+
caveat: import_zod2.z.string()
|
|
32047
|
+
}),
|
|
32048
|
+
metrics: import_zod2.z.object({
|
|
32049
|
+
roa: import_zod2.z.string().optional(),
|
|
32050
|
+
roe: import_zod2.z.string().optional(),
|
|
32051
|
+
tier1_leverage: import_zod2.z.string().optional(),
|
|
32052
|
+
noncurrent_loans: import_zod2.z.string().optional(),
|
|
32053
|
+
loan_to_deposit: import_zod2.z.string().optional(),
|
|
32054
|
+
net_interest_margin: import_zod2.z.string().optional(),
|
|
32055
|
+
efficiency_ratio: import_zod2.z.string().optional()
|
|
32056
|
+
}),
|
|
32057
|
+
risk_signals: import_zod2.z.array(import_zod2.z.string()),
|
|
32058
|
+
warnings: import_zod2.z.array(import_zod2.z.string()),
|
|
32059
|
+
sources: import_zod2.z.array(Source)
|
|
32060
|
+
});
|
|
32061
|
+
var FdicAnalysisOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32062
|
+
|
|
31894
32063
|
// src/tools/institutions.ts
|
|
31895
32064
|
function registerInstitutionTools(server) {
|
|
31896
32065
|
server.registerTool(
|
|
31897
32066
|
"fdic_search_institutions",
|
|
31898
32067
|
{
|
|
31899
32068
|
title: "Search FDIC Institutions",
|
|
31900
|
-
description:
|
|
31901
|
-
|
|
31902
|
-
Returns institution profile data including name, location, charter class, asset size, deposit totals, profitability metrics, and regulatory status.
|
|
31903
|
-
|
|
31904
|
-
Common filter examples:
|
|
31905
|
-
- By state: STNAME:"California"
|
|
31906
|
-
- Active banks only: ACTIVE:1
|
|
31907
|
-
- Large banks: ASSET:[10000000 TO *] (assets in $thousands)
|
|
31908
|
-
- By bank class: BKCLASS:N (national bank), BKCLASS:SM (state member bank), BKCLASS:NM (state non-member)
|
|
31909
|
-
- By name: NAME:"Wells Fargo"
|
|
31910
|
-
- Commercial banks: CB:1
|
|
31911
|
-
- Savings institutions: MUTUAL:1
|
|
31912
|
-
- Recently established: ESTYMD:[2010-01-01 TO *]
|
|
31913
|
-
|
|
31914
|
-
Charter class codes (BKCLASS):
|
|
31915
|
-
N = National commercial bank (OCC-supervised)
|
|
31916
|
-
SM = State-chartered, Federal Reserve member
|
|
31917
|
-
NM = State-chartered, non-member (FDIC-supervised)
|
|
31918
|
-
SB = Federal savings bank (OCC-supervised)
|
|
31919
|
-
SA = State savings association
|
|
31920
|
-
OI = Insured branch of foreign bank
|
|
31921
|
-
|
|
31922
|
-
Key returned fields:
|
|
31923
|
-
- CERT: FDIC Certificate Number (unique ID)
|
|
31924
|
-
- NAME: Institution name
|
|
31925
|
-
- CITY, STALP (two-letter state code), STNAME (full state name): Location
|
|
31926
|
-
- ASSET: Total assets ($thousands)
|
|
31927
|
-
- DEP: Total deposits ($thousands)
|
|
31928
|
-
- BKCLASS: Charter class code (see above)
|
|
31929
|
-
- ACTIVE: 1 if currently active, 0 if inactive
|
|
31930
|
-
- ROA, ROE: Profitability ratios
|
|
31931
|
-
- OFFICES: Number of branch offices
|
|
31932
|
-
- ESTYMD: Establishment date (YYYY-MM-DD)
|
|
31933
|
-
- REGAGNT: Primary federal regulator (OCC, FRS, FDIC)
|
|
31934
|
-
|
|
31935
|
-
Args:
|
|
31936
|
-
- filters (string, optional): ElasticSearch query filter
|
|
31937
|
-
- fields (string, optional): Comma-separated field names
|
|
31938
|
-
- limit (number): Records to return, 1-10000 (default: 20)
|
|
31939
|
-
- offset (number): Pagination offset (default: 0)
|
|
31940
|
-
- sort_by (string, optional): Field to sort by
|
|
31941
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
31942
|
-
|
|
31943
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and institution records.`,
|
|
32069
|
+
description: "Use this when the user needs FDIC-insured institution search results by name, state, CERT, asset size, charter class, or regulatory status. Returns institution profile rows with pagination; use fdic://schemas/institutions for the full field catalog.",
|
|
31944
32070
|
inputSchema: CommonQuerySchema,
|
|
32071
|
+
outputSchema: FdicInstitutionsSearchOutputSchema,
|
|
31945
32072
|
annotations: {
|
|
31946
32073
|
readOnlyHint: true,
|
|
31947
32074
|
destructiveHint: false,
|
|
@@ -31958,7 +32085,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
31958
32085
|
params.offset ?? 0,
|
|
31959
32086
|
records.length
|
|
31960
32087
|
);
|
|
31961
|
-
const output =
|
|
32088
|
+
const output = capStructuredContent(
|
|
32089
|
+
{ ...pagination, institutions: records },
|
|
32090
|
+
"institutions"
|
|
32091
|
+
);
|
|
31962
32092
|
const text = truncateIfNeeded(
|
|
31963
32093
|
formatSearchResultText("institutions", records, pagination, [
|
|
31964
32094
|
"CERT",
|
|
@@ -31984,16 +32114,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
31984
32114
|
"fdic_get_institution",
|
|
31985
32115
|
{
|
|
31986
32116
|
title: "Get Institution by Certificate Number",
|
|
31987
|
-
description:
|
|
31988
|
-
|
|
31989
|
-
Use this when you know the exact CERT number for an institution. To find a CERT number, use fdic_search_institutions first.
|
|
31990
|
-
|
|
31991
|
-
Args:
|
|
31992
|
-
- cert (number): FDIC Certificate Number (e.g., 3511 for Bank of America)
|
|
31993
|
-
- fields (string, optional): Comma-separated list of fields to return
|
|
31994
|
-
|
|
31995
|
-
Returns a detailed institution profile suitable for concise summaries, with structured fields available for exact values when needed.`,
|
|
32117
|
+
description: "Use this when the user knows an exact FDIC Certificate Number and needs one institution profile. To discover a CERT first, call fdic_search_institutions or fdic_search.",
|
|
31996
32118
|
inputSchema: CertSchema,
|
|
32119
|
+
outputSchema: FdicInstitutionLookupOutputSchema,
|
|
31997
32120
|
annotations: {
|
|
31998
32121
|
readOnlyHint: true,
|
|
31999
32122
|
destructiveHint: false,
|
|
@@ -32048,43 +32171,9 @@ function registerFailureTools(server) {
|
|
|
32048
32171
|
"fdic_search_failures",
|
|
32049
32172
|
{
|
|
32050
32173
|
title: "Search Bank Failures",
|
|
32051
|
-
description:
|
|
32052
|
-
|
|
32053
|
-
Returns data on bank failures including failure date, resolution type, estimated cost to the FDIC Deposit Insurance Fund, and acquiring institution info.
|
|
32054
|
-
|
|
32055
|
-
Common filter examples:
|
|
32056
|
-
- By state: STALP:CA (two-letter state code)
|
|
32057
|
-
- By year range: FAILDATE:[2008-01-01 TO 2010-12-31]
|
|
32058
|
-
- Recent failures: FAILDATE:[2020-01-01 TO *]
|
|
32059
|
-
- By resolution type: RESTYPE:PAYOFF or RESTYPE:"PURCHASE AND ASSUMPTION"
|
|
32060
|
-
- Large failures by cost: COST:[100000 TO *] (cost in $thousands)
|
|
32061
|
-
- By name: NAME:"Washington Mutual"
|
|
32062
|
-
|
|
32063
|
-
Resolution types (RESTYPE):
|
|
32064
|
-
PAYOFF = depositors paid directly, no acquirer
|
|
32065
|
-
PURCHASE AND ASSUMPTION = acquirer buys assets and assumes deposits
|
|
32066
|
-
PAYOUT = variant of payoff with insured-deposit transfer
|
|
32067
|
-
|
|
32068
|
-
Key returned fields:
|
|
32069
|
-
- CERT: FDIC Certificate Number
|
|
32070
|
-
- NAME: Institution name
|
|
32071
|
-
- CITY, STALP (two-letter state code), STNAME (full state name): Location
|
|
32072
|
-
- FAILDATE: Date of failure (YYYY-MM-DD)
|
|
32073
|
-
- SAVR: Savings association flag (SA) or bank (BK)
|
|
32074
|
-
- RESTYPE: Resolution type (see above)
|
|
32075
|
-
- QBFASSET: Total assets at failure ($thousands)
|
|
32076
|
-
- COST: Estimated cost to FDIC Deposit Insurance Fund ($thousands)
|
|
32077
|
-
|
|
32078
|
-
Args:
|
|
32079
|
-
- filters (string, optional): ElasticSearch query filter
|
|
32080
|
-
- fields (string, optional): Comma-separated field names
|
|
32081
|
-
- limit (number): Records to return (default: 20)
|
|
32082
|
-
- offset (number): Pagination offset (default: 0)
|
|
32083
|
-
- sort_by (string, optional): Field to sort by (e.g., FAILDATE, COST)
|
|
32084
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32085
|
-
|
|
32086
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and failure records.`,
|
|
32174
|
+
description: "Use this when the user wants details on failed FDIC-insured institutions filtered by name, state, date range, resolution type, or cost. Returns failure records with pagination; see fdic://schemas/failures for the full field catalog.",
|
|
32087
32175
|
inputSchema: CommonQuerySchema,
|
|
32176
|
+
outputSchema: FdicFailuresSearchOutputSchema,
|
|
32088
32177
|
annotations: {
|
|
32089
32178
|
readOnlyHint: true,
|
|
32090
32179
|
destructiveHint: false,
|
|
@@ -32101,7 +32190,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32101
32190
|
params.offset ?? 0,
|
|
32102
32191
|
records.length
|
|
32103
32192
|
);
|
|
32104
|
-
const output =
|
|
32193
|
+
const output = capStructuredContent(
|
|
32194
|
+
{ ...pagination, failures: records },
|
|
32195
|
+
"failures"
|
|
32196
|
+
);
|
|
32105
32197
|
const text = truncateIfNeeded(
|
|
32106
32198
|
formatSearchResultText("failures", records, pagination, [
|
|
32107
32199
|
"CERT",
|
|
@@ -32128,16 +32220,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32128
32220
|
"fdic_get_institution_failure",
|
|
32129
32221
|
{
|
|
32130
32222
|
title: "Get Failure Details by Certificate Number",
|
|
32131
|
-
description:
|
|
32132
|
-
|
|
32133
|
-
Use this when you know the CERT of a failed institution to get its specific failure record.
|
|
32134
|
-
|
|
32135
|
-
Args:
|
|
32136
|
-
- cert (number): FDIC Certificate Number of the failed institution
|
|
32137
|
-
- fields (string, optional): Comma-separated list of fields to return
|
|
32138
|
-
|
|
32139
|
-
Returns detailed failure information suitable for concise summaries, with structured fields available for exact values when needed.`,
|
|
32223
|
+
description: "Use this when the user knows the CERT of a failed institution and needs its specific failure record. Returns failure details (date, resolution type, cost, acquirer); responds with `found: false` if the institution did not fail.",
|
|
32140
32224
|
inputSchema: CertSchema,
|
|
32225
|
+
outputSchema: FdicFailureLookupOutputSchema,
|
|
32141
32226
|
annotations: {
|
|
32142
32227
|
readOnlyHint: true,
|
|
32143
32228
|
destructiveHint: false,
|
|
@@ -32187,7 +32272,7 @@ Returns detailed failure information suitable for concise summaries, with struct
|
|
|
32187
32272
|
}
|
|
32188
32273
|
|
|
32189
32274
|
// src/tools/locations.ts
|
|
32190
|
-
var
|
|
32275
|
+
var import_zod3 = require("zod");
|
|
32191
32276
|
|
|
32192
32277
|
// src/tools/shared/queryUtils.ts
|
|
32193
32278
|
var CHUNK_SIZE = 25;
|
|
@@ -32283,7 +32368,7 @@ async function mapWithConcurrency(values, limit, mapper) {
|
|
|
32283
32368
|
|
|
32284
32369
|
// src/tools/locations.ts
|
|
32285
32370
|
var LocationQuerySchema = CommonQuerySchema.extend({
|
|
32286
|
-
cert:
|
|
32371
|
+
cert: import_zod3.z.number().int().positive().optional().describe(
|
|
32287
32372
|
"Filter by FDIC Certificate Number to get all branches of a specific institution"
|
|
32288
32373
|
)
|
|
32289
32374
|
});
|
|
@@ -32292,53 +32377,9 @@ function registerLocationTools(server) {
|
|
|
32292
32377
|
"fdic_search_locations",
|
|
32293
32378
|
{
|
|
32294
32379
|
title: "Search Institution Locations / Branches",
|
|
32295
|
-
description:
|
|
32296
|
-
|
|
32297
|
-
Returns branch/office data including address, city, state, coordinates, branch type, and establishment date.
|
|
32298
|
-
|
|
32299
|
-
Common filter examples:
|
|
32300
|
-
- All branches of a bank: CERT:3511
|
|
32301
|
-
- By state: STALP:TX (two-letter state code)
|
|
32302
|
-
- By city: CITY:"Austin"
|
|
32303
|
-
- Main offices only: BRNUM:0
|
|
32304
|
-
- By county: COUNTY:"Travis"
|
|
32305
|
-
- Active branches only: ENDEFYMD:[9999-01-01 TO *] (sentinel date 9999-12-31 means still open)
|
|
32306
|
-
- By metro area (CBSA): CBSA_METRO_NAME:"New York-Newark-Jersey City"
|
|
32307
|
-
|
|
32308
|
-
Branch service types (BRSERTYP):
|
|
32309
|
-
11 = Full service brick and mortar
|
|
32310
|
-
12 = Full service retail
|
|
32311
|
-
21 = Limited service administrative
|
|
32312
|
-
22 = Limited service military
|
|
32313
|
-
23 = Limited service drive-through
|
|
32314
|
-
24 = Limited service loan production
|
|
32315
|
-
25 = Limited service consumer/trust
|
|
32316
|
-
26 = Limited service Internet/mobile
|
|
32317
|
-
29 = Limited service other
|
|
32318
|
-
|
|
32319
|
-
Key returned fields:
|
|
32320
|
-
- CERT: FDIC Certificate Number
|
|
32321
|
-
- UNINAME: Institution name
|
|
32322
|
-
- NAMEFULL: Full branch name
|
|
32323
|
-
- ADDRESS, CITY, STALP (two-letter state code), ZIP: Branch address
|
|
32324
|
-
- COUNTY: County name
|
|
32325
|
-
- BRNUM: Branch number (0 = main office)
|
|
32326
|
-
- BRSERTYP: Branch service type code (see above)
|
|
32327
|
-
- LATITUDE, LONGITUDE: Geographic coordinates
|
|
32328
|
-
- ESTYMD: Branch established date (YYYY-MM-DD)
|
|
32329
|
-
- ENDEFYMD: Branch end date (9999-12-31 if still active)
|
|
32330
|
-
|
|
32331
|
-
Args:
|
|
32332
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32333
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32334
|
-
- fields (string, optional): Comma-separated field names
|
|
32335
|
-
- limit (number): Records to return (default: 20)
|
|
32336
|
-
- offset (number): Pagination offset (default: 0)
|
|
32337
|
-
- sort_by (string, optional): Field to sort by
|
|
32338
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32339
|
-
|
|
32340
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and branch location records.`,
|
|
32380
|
+
description: "Use this when the user wants branch/office locations for FDIC-insured institutions, filtered by CERT, state, city, county, metro area, or branch type. Returns address, coordinates, branch number, and service-type rows; see fdic://schemas/locations for the full field catalog.",
|
|
32341
32381
|
inputSchema: LocationQuerySchema,
|
|
32382
|
+
outputSchema: FdicLocationsSearchOutputSchema,
|
|
32342
32383
|
annotations: {
|
|
32343
32384
|
readOnlyHint: true,
|
|
32344
32385
|
destructiveHint: false,
|
|
@@ -32362,7 +32403,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32362
32403
|
params.offset ?? 0,
|
|
32363
32404
|
records.length
|
|
32364
32405
|
);
|
|
32365
|
-
const output =
|
|
32406
|
+
const output = capStructuredContent(
|
|
32407
|
+
{ ...pagination, locations: records },
|
|
32408
|
+
"locations"
|
|
32409
|
+
);
|
|
32366
32410
|
const text = truncateIfNeeded(
|
|
32367
32411
|
formatSearchResultText("locations", records, pagination, [
|
|
32368
32412
|
"CERT",
|
|
@@ -32387,9 +32431,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32387
32431
|
}
|
|
32388
32432
|
|
|
32389
32433
|
// src/tools/history.ts
|
|
32390
|
-
var
|
|
32434
|
+
var import_zod4 = require("zod");
|
|
32391
32435
|
var HistoryQuerySchema = CommonQuerySchema.extend({
|
|
32392
|
-
cert:
|
|
32436
|
+
cert: import_zod4.z.number().int().positive().optional().describe(
|
|
32393
32437
|
"Filter by FDIC Certificate Number to get history for a specific institution"
|
|
32394
32438
|
)
|
|
32395
32439
|
});
|
|
@@ -32398,57 +32442,9 @@ function registerHistoryTools(server) {
|
|
|
32398
32442
|
"fdic_search_history",
|
|
32399
32443
|
{
|
|
32400
32444
|
title: "Search Institution History / Structure Changes",
|
|
32401
|
-
description:
|
|
32402
|
-
|
|
32403
|
-
Returns records on mergers, acquisitions, name changes, charter conversions, failures, and other significant structural events.
|
|
32404
|
-
|
|
32405
|
-
Common filter examples:
|
|
32406
|
-
- History for a specific bank: CERT:3511
|
|
32407
|
-
- Mergers: TYPE:merger
|
|
32408
|
-
- Failures: TYPE:failure
|
|
32409
|
-
- Name changes: CHANGECODE:CO
|
|
32410
|
-
- By date range: PROCDATE:[2008-01-01 TO 2009-12-31]
|
|
32411
|
-
- By state: PSTALP:CA (two-letter state code)
|
|
32412
|
-
|
|
32413
|
-
Event types (TYPE):
|
|
32414
|
-
merger = institution was merged into another
|
|
32415
|
-
failure = institution failed
|
|
32416
|
-
assistance = received FDIC assistance transaction
|
|
32417
|
-
insurance = insurance-related event (new coverage, termination)
|
|
32418
|
-
|
|
32419
|
-
Common change codes (CHANGECODE):
|
|
32420
|
-
CO = name change
|
|
32421
|
-
CR = charter conversion
|
|
32422
|
-
DC = deposit assumption change
|
|
32423
|
-
MA = merger/acquisition (absorbed by another institution)
|
|
32424
|
-
NI = new institution insured
|
|
32425
|
-
TC = trust company conversion
|
|
32426
|
-
|
|
32427
|
-
Key returned fields:
|
|
32428
|
-
- CERT: FDIC Certificate Number
|
|
32429
|
-
- INSTNAME: Institution name
|
|
32430
|
-
- CLASS: Charter class at time of change
|
|
32431
|
-
- PCITY, PSTALP: Location (city, two-letter state code)
|
|
32432
|
-
- PROCDATE: Processing date of the change (YYYY-MM-DD)
|
|
32433
|
-
- EFFDATE: Effective date of the change (YYYY-MM-DD)
|
|
32434
|
-
- ENDEFYMD: End effective date
|
|
32435
|
-
- PCERT: Predecessor/successor CERT (for mergers)
|
|
32436
|
-
- TYPE: Type of structural change (see above)
|
|
32437
|
-
- CHANGECODE: Code for type of change (see above)
|
|
32438
|
-
- CHANGECODE_DESC: Human-readable description of the change code
|
|
32439
|
-
- INSDATE: Insurance date
|
|
32440
|
-
|
|
32441
|
-
Args:
|
|
32442
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32443
|
-
- filters (string, optional): ElasticSearch query filters
|
|
32444
|
-
- fields (string, optional): Comma-separated field names
|
|
32445
|
-
- limit (number): Records to return (default: 20)
|
|
32446
|
-
- offset (number): Pagination offset (default: 0)
|
|
32447
|
-
- sort_by (string, optional): Field to sort by (e.g., PROCDATE)
|
|
32448
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32449
|
-
|
|
32450
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and event records.`,
|
|
32445
|
+
description: "Use this when the user wants structural-change events (mergers, acquisitions, name changes, charter conversions, failures) for FDIC-insured institutions, filtered by CERT, type, change code, date range, or state. See fdic://schemas/history for the full field catalog.",
|
|
32451
32446
|
inputSchema: HistoryQuerySchema,
|
|
32447
|
+
outputSchema: FdicHistorySearchOutputSchema,
|
|
32452
32448
|
annotations: {
|
|
32453
32449
|
readOnlyHint: true,
|
|
32454
32450
|
destructiveHint: false,
|
|
@@ -32472,7 +32468,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32472
32468
|
params.offset ?? 0,
|
|
32473
32469
|
records.length
|
|
32474
32470
|
);
|
|
32475
|
-
const output =
|
|
32471
|
+
const output = capStructuredContent(
|
|
32472
|
+
{ ...pagination, events: records },
|
|
32473
|
+
"events"
|
|
32474
|
+
);
|
|
32476
32475
|
const text = truncateIfNeeded(
|
|
32477
32476
|
formatSearchResultText("events", records, pagination, [
|
|
32478
32477
|
"CERT",
|
|
@@ -32497,63 +32496,30 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32497
32496
|
}
|
|
32498
32497
|
|
|
32499
32498
|
// src/tools/financials.ts
|
|
32500
|
-
var
|
|
32499
|
+
var import_zod5 = require("zod");
|
|
32501
32500
|
var FinancialQuerySchema = CommonQuerySchema.extend({
|
|
32502
|
-
sort_order:
|
|
32501
|
+
sort_order: import_zod5.z.enum(["ASC", "DESC"]).default("DESC").describe(
|
|
32503
32502
|
"Sort direction: DESC (descending, default for most recent first) or ASC (ascending)"
|
|
32504
32503
|
),
|
|
32505
|
-
cert:
|
|
32504
|
+
cert: import_zod5.z.number().int().positive().optional().describe(
|
|
32506
32505
|
"Filter by FDIC Certificate Number to get financials for a specific institution"
|
|
32507
32506
|
),
|
|
32508
|
-
repdte:
|
|
32509
|
-
"Filter by Report Date (REPDTE) in YYYYMMDD format
|
|
32507
|
+
repdte: import_zod5.z.string().optional().describe(
|
|
32508
|
+
"Filter by Report Date (REPDTE) in YYYYMMDD format (quarter-end: 0331, 0630, 0930, 1231). If omitted, returns all available dates (sorted most recent first)."
|
|
32510
32509
|
)
|
|
32511
32510
|
});
|
|
32512
32511
|
var SummaryQuerySchema = CommonQuerySchema.extend({
|
|
32513
|
-
cert:
|
|
32514
|
-
year:
|
|
32512
|
+
cert: import_zod5.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32513
|
+
year: import_zod5.z.number().int().min(1934).optional().describe("Filter by specific year (e.g., 2022)")
|
|
32515
32514
|
});
|
|
32516
32515
|
function registerFinancialTools(server) {
|
|
32517
32516
|
server.registerTool(
|
|
32518
32517
|
"fdic_search_financials",
|
|
32519
32518
|
{
|
|
32520
32519
|
title: "Search Institution Financial Data",
|
|
32521
|
-
description:
|
|
32522
|
-
|
|
32523
|
-
Returns balance sheet, income statement, capital, and performance ratio data from FDIC Call Reports.
|
|
32524
|
-
|
|
32525
|
-
Common filter examples:
|
|
32526
|
-
- Financials for a specific bank: CERT:3511
|
|
32527
|
-
- By report date: REPDTE:20231231
|
|
32528
|
-
- High-profit banks in Q4 2023: REPDTE:20231231 AND ROA:[1.5 TO *]
|
|
32529
|
-
- Large banks most recent: ASSET:[10000000 TO *]
|
|
32530
|
-
- Negative net income: NETINC:[* TO 0]
|
|
32531
|
-
|
|
32532
|
-
Key returned fields:
|
|
32533
|
-
- CERT: FDIC Certificate Number
|
|
32534
|
-
- REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
|
|
32535
|
-
- ASSET: Total assets ($thousands)
|
|
32536
|
-
- DEP: Total deposits ($thousands)
|
|
32537
|
-
- DEPDOM: Domestic deposits ($thousands)
|
|
32538
|
-
- INTINC: Total interest income ($thousands)
|
|
32539
|
-
- EINTEXP: Total interest expense ($thousands)
|
|
32540
|
-
- NETINC: Net income ($thousands)
|
|
32541
|
-
- ROA: Return on assets (%)
|
|
32542
|
-
- ROE: Return on equity (%)
|
|
32543
|
-
- NETNIM: Net interest margin (%)
|
|
32544
|
-
|
|
32545
|
-
Args:
|
|
32546
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32547
|
-
- repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
|
|
32548
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32549
|
-
- fields (string, optional): Comma-separated field names (the full set has 1,100+ fields)
|
|
32550
|
-
- limit (number): Records to return (default: 20)
|
|
32551
|
-
- offset (number): Pagination offset (default: 0)
|
|
32552
|
-
- sort_by (string, optional): Field to sort by
|
|
32553
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'DESC' recommended for most recent first)
|
|
32554
|
-
|
|
32555
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and quarterly financial records.`,
|
|
32520
|
+
description: "Use this when the user wants quarterly Call Report data (balance sheet, income, capital, performance ratios) for FDIC-insured institutions. Filter by CERT and/or REPDTE plus optional ElasticSearch filters. See fdic://schemas/financials for the full 1,100+ field catalog.",
|
|
32556
32521
|
inputSchema: FinancialQuerySchema,
|
|
32522
|
+
outputSchema: FdicFinancialsSearchOutputSchema,
|
|
32557
32523
|
annotations: {
|
|
32558
32524
|
readOnlyHint: true,
|
|
32559
32525
|
destructiveHint: false,
|
|
@@ -32578,7 +32544,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32578
32544
|
params.offset ?? 0,
|
|
32579
32545
|
records.length
|
|
32580
32546
|
);
|
|
32581
|
-
const output =
|
|
32547
|
+
const output = capStructuredContent(
|
|
32548
|
+
{ ...pagination, financials: records },
|
|
32549
|
+
"financials"
|
|
32550
|
+
);
|
|
32582
32551
|
const text = truncateIfNeeded(
|
|
32583
32552
|
formatSearchResultText("financial records", records, pagination, [
|
|
32584
32553
|
"CERT",
|
|
@@ -32604,40 +32573,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32604
32573
|
"fdic_search_summary",
|
|
32605
32574
|
{
|
|
32606
32575
|
title: "Search Annual Financial Summary Data",
|
|
32607
|
-
description:
|
|
32608
|
-
|
|
32609
|
-
Returns annual snapshots of key financial metrics \u2014 useful for tracking an institution's growth over time.
|
|
32610
|
-
|
|
32611
|
-
Common filter examples:
|
|
32612
|
-
- Annual history for a bank: CERT:3511
|
|
32613
|
-
- Specific year: YEAR:2022
|
|
32614
|
-
- Year range: YEAR:[2010 TO 2020]
|
|
32615
|
-
- Large banks in 2022: YEAR:2022 AND ASSET:[10000000 TO *]
|
|
32616
|
-
- Profitable in 2023: YEAR:2023 AND ROE:[10 TO *]
|
|
32617
|
-
|
|
32618
|
-
Key returned fields:
|
|
32619
|
-
- CERT: FDIC Certificate Number
|
|
32620
|
-
- YEAR: Report year
|
|
32621
|
-
- ASSET: Total assets ($thousands)
|
|
32622
|
-
- DEP: Total deposits ($thousands)
|
|
32623
|
-
- NETINC: Net income ($thousands)
|
|
32624
|
-
- ROA: Return on assets (%)
|
|
32625
|
-
- ROE: Return on equity (%)
|
|
32626
|
-
- OFFICES: Number of branch offices
|
|
32627
|
-
- REPDTE: Report Date \u2014 the last day of the reporting period (YYYYMMDD)
|
|
32628
|
-
|
|
32629
|
-
Args:
|
|
32630
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32631
|
-
- year (number, optional): Filter by specific year (1934-present)
|
|
32632
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32633
|
-
- fields (string, optional): Comma-separated field names
|
|
32634
|
-
- limit (number): Records to return (default: 20)
|
|
32635
|
-
- offset (number): Pagination offset (default: 0)
|
|
32636
|
-
- sort_by (string, optional): Field to sort by (e.g., YEAR, ASSET)
|
|
32637
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32638
|
-
|
|
32639
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and annual summary records.`,
|
|
32576
|
+
description: "Use this when the user wants annual financial-summary snapshots (assets, deposits, ROA, ROE, offices) for FDIC-insured institutions, filtered by CERT and/or year. See fdic://schemas/summary for the full field catalog.",
|
|
32640
32577
|
inputSchema: SummaryQuerySchema,
|
|
32578
|
+
outputSchema: FdicSummarySearchOutputSchema,
|
|
32641
32579
|
annotations: {
|
|
32642
32580
|
readOnlyHint: true,
|
|
32643
32581
|
destructiveHint: false,
|
|
@@ -32662,7 +32600,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32662
32600
|
params.offset ?? 0,
|
|
32663
32601
|
records.length
|
|
32664
32602
|
);
|
|
32665
|
-
const output =
|
|
32603
|
+
const output = capStructuredContent(
|
|
32604
|
+
{ ...pagination, summary: records },
|
|
32605
|
+
"summary"
|
|
32606
|
+
);
|
|
32666
32607
|
const text = truncateIfNeeded(
|
|
32667
32608
|
formatSearchResultText("annual summary records", records, pagination, [
|
|
32668
32609
|
"CERT",
|
|
@@ -32687,10 +32628,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32687
32628
|
}
|
|
32688
32629
|
|
|
32689
32630
|
// src/tools/sod.ts
|
|
32690
|
-
var
|
|
32631
|
+
var import_zod6 = require("zod");
|
|
32691
32632
|
var SodQuerySchema = CommonQuerySchema.extend({
|
|
32692
|
-
cert:
|
|
32693
|
-
year:
|
|
32633
|
+
cert: import_zod6.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32634
|
+
year: import_zod6.z.number().int().min(1994).optional().describe(
|
|
32694
32635
|
"Filter by specific year (1994-present). SOD data is annual."
|
|
32695
32636
|
)
|
|
32696
32637
|
});
|
|
@@ -32699,40 +32640,9 @@ function registerSodTools(server) {
|
|
|
32699
32640
|
"fdic_search_sod",
|
|
32700
32641
|
{
|
|
32701
32642
|
title: "Search Summary of Deposits (SOD)",
|
|
32702
|
-
description:
|
|
32703
|
-
|
|
32704
|
-
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.
|
|
32705
|
-
|
|
32706
|
-
Common filter examples:
|
|
32707
|
-
- All branches for a bank: CERT:3511
|
|
32708
|
-
- SOD for specific year: YEAR:2022
|
|
32709
|
-
- Branches in a state: STALPBR:CA
|
|
32710
|
-
- Branches in a city: CITYBR:"Austin"
|
|
32711
|
-
- High-deposit branches: DEPSUMBR:[1000000 TO *]
|
|
32712
|
-
- By metro area (MSA code): MSABR:19100
|
|
32713
|
-
|
|
32714
|
-
Key returned fields:
|
|
32715
|
-
- YEAR: Report year (as of June 30)
|
|
32716
|
-
- CERT: FDIC Certificate Number
|
|
32717
|
-
- BRNUM: Branch number (0 = main office)
|
|
32718
|
-
- NAMEFULL: Branch or institution name
|
|
32719
|
-
- ADDRESBR, CITYBR, STALPBR, ZIPBR: Branch address
|
|
32720
|
-
- DEPSUMBR: Total deposits at branch ($thousands)
|
|
32721
|
-
- MSABR: Metropolitan Statistical Area code (numeric; 0 = non-MSA)
|
|
32722
|
-
- LATITUDE, LONGITUDE: Coordinates
|
|
32723
|
-
|
|
32724
|
-
Args:
|
|
32725
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32726
|
-
- year (number, optional): SOD report year (1994-present)
|
|
32727
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32728
|
-
- fields (string, optional): Comma-separated field names
|
|
32729
|
-
- limit (number): Records to return (default: 20)
|
|
32730
|
-
- offset (number): Pagination offset (default: 0)
|
|
32731
|
-
- sort_by (string, optional): Field to sort by (e.g., DEPSUMBR, YEAR)
|
|
32732
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32733
|
-
|
|
32734
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and deposit records.`,
|
|
32643
|
+
description: "Use this when the user wants annual branch-level deposit data (SOD, as of June 30 each year) \u2014 branch deposits, MSAs, geographic distribution. Filter by CERT and/or year. See fdic://schemas/sod for the full field catalog.",
|
|
32735
32644
|
inputSchema: SodQuerySchema,
|
|
32645
|
+
outputSchema: FdicSodSearchOutputSchema,
|
|
32736
32646
|
annotations: {
|
|
32737
32647
|
readOnlyHint: true,
|
|
32738
32648
|
destructiveHint: false,
|
|
@@ -32757,7 +32667,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32757
32667
|
params.offset ?? 0,
|
|
32758
32668
|
records.length
|
|
32759
32669
|
);
|
|
32760
|
-
const output =
|
|
32670
|
+
const output = capStructuredContent(
|
|
32671
|
+
{ ...pagination, deposits: records },
|
|
32672
|
+
"deposits"
|
|
32673
|
+
);
|
|
32761
32674
|
const text = truncateIfNeeded(
|
|
32762
32675
|
formatSearchResultText("deposit records", records, pagination, [
|
|
32763
32676
|
"CERT",
|
|
@@ -32782,11 +32695,11 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32782
32695
|
}
|
|
32783
32696
|
|
|
32784
32697
|
// src/tools/demographics.ts
|
|
32785
|
-
var
|
|
32698
|
+
var import_zod7 = require("zod");
|
|
32786
32699
|
var DemographicsQuerySchema = CommonQuerySchema.extend({
|
|
32787
|
-
cert:
|
|
32788
|
-
repdte:
|
|
32789
|
-
"Filter by Report Date (REPDTE) in YYYYMMDD format
|
|
32700
|
+
cert: import_zod7.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32701
|
+
repdte: import_zod7.z.string().optional().describe(
|
|
32702
|
+
"Filter by Report Date (REPDTE) in YYYYMMDD format (quarter-end: 0331, 0630, 0930, 1231)."
|
|
32790
32703
|
)
|
|
32791
32704
|
});
|
|
32792
32705
|
function registerDemographicsTools(server) {
|
|
@@ -32794,43 +32707,9 @@ function registerDemographicsTools(server) {
|
|
|
32794
32707
|
"fdic_search_demographics",
|
|
32795
32708
|
{
|
|
32796
32709
|
title: "Search Institution Demographics Data",
|
|
32797
|
-
description:
|
|
32798
|
-
|
|
32799
|
-
Returns quarterly demographic and market-structure attributes such as office counts, territory assignments, metro classification, county/country codes, and selected geographic reference data.
|
|
32800
|
-
|
|
32801
|
-
Common filter examples:
|
|
32802
|
-
- Demographics for a specific bank: CERT:3511
|
|
32803
|
-
- By report date: REPDTE:20251231
|
|
32804
|
-
- Institutions in metro areas: METRO:1
|
|
32805
|
-
- Institutions with out-of-state offices: OFFSTATE:[1 TO *]
|
|
32806
|
-
- Minority status date present: MNRTYDTE:[19000101 TO 99991231]
|
|
32807
|
-
|
|
32808
|
-
Key returned fields:
|
|
32809
|
-
- CERT: FDIC Certificate Number
|
|
32810
|
-
- REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
|
|
32811
|
-
- QTRNO: Quarter number
|
|
32812
|
-
- OFFTOT: Total offices
|
|
32813
|
-
- OFFSTATE: Offices in other states
|
|
32814
|
-
- OFFNDOM: Offices in non-domestic territories
|
|
32815
|
-
- OFFOTH: Other offices
|
|
32816
|
-
- OFFSOD: Offices included in Summary of Deposits
|
|
32817
|
-
- METRO, MICRO: Metro/micro area flags
|
|
32818
|
-
- CBSANAME, CSA: Core-based statistical area data
|
|
32819
|
-
- FDICTERR, RISKTERR: FDIC and risk territory assignments
|
|
32820
|
-
- SIMS_LAT, SIMS_LONG: Geographic coordinates
|
|
32821
|
-
|
|
32822
|
-
Args:
|
|
32823
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32824
|
-
- repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
|
|
32825
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32826
|
-
- fields (string, optional): Comma-separated field names
|
|
32827
|
-
- limit (number): Records to return (default: 20)
|
|
32828
|
-
- offset (number): Pagination offset (default: 0)
|
|
32829
|
-
- sort_by (string, optional): Field to sort by
|
|
32830
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32831
|
-
|
|
32832
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and demographic records.`,
|
|
32710
|
+
description: "Use this when the user wants quarterly demographic and market-structure attributes (office counts, metro classification, county/territory codes, geographic reference data) for FDIC-insured institutions. Filter by CERT and/or REPDTE. See fdic://schemas/demographics for the full field catalog.",
|
|
32833
32711
|
inputSchema: DemographicsQuerySchema,
|
|
32712
|
+
outputSchema: FdicDemographicsSearchOutputSchema,
|
|
32834
32713
|
annotations: {
|
|
32835
32714
|
readOnlyHint: true,
|
|
32836
32715
|
destructiveHint: false,
|
|
@@ -32855,7 +32734,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32855
32734
|
params.offset ?? 0,
|
|
32856
32735
|
records.length
|
|
32857
32736
|
);
|
|
32858
|
-
const output =
|
|
32737
|
+
const output = capStructuredContent(
|
|
32738
|
+
{ ...pagination, demographics: records },
|
|
32739
|
+
"demographics"
|
|
32740
|
+
);
|
|
32859
32741
|
const text = truncateIfNeeded(
|
|
32860
32742
|
formatSearchResultText("demographic records", records, pagination, [
|
|
32861
32743
|
"CERT",
|
|
@@ -32880,7 +32762,7 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32880
32762
|
}
|
|
32881
32763
|
|
|
32882
32764
|
// src/tools/analysis.ts
|
|
32883
|
-
var
|
|
32765
|
+
var import_zod8 = require("zod");
|
|
32884
32766
|
|
|
32885
32767
|
// src/tools/shared/progress.ts
|
|
32886
32768
|
function asProgressToken(value) {
|
|
@@ -32906,7 +32788,7 @@ async function sendProgressNotification(sender, progressToken, progress, message
|
|
|
32906
32788
|
}
|
|
32907
32789
|
|
|
32908
32790
|
// src/tools/analysis.ts
|
|
32909
|
-
var SortFieldSchema =
|
|
32791
|
+
var SortFieldSchema = import_zod8.z.enum([
|
|
32910
32792
|
"asset_growth",
|
|
32911
32793
|
"asset_growth_pct",
|
|
32912
32794
|
"dep_growth",
|
|
@@ -32920,35 +32802,35 @@ var SortFieldSchema = import_zod7.z.enum([
|
|
|
32920
32802
|
"deposits_per_office_change",
|
|
32921
32803
|
"deposits_to_assets_change"
|
|
32922
32804
|
]);
|
|
32923
|
-
var AnalysisModeSchema =
|
|
32924
|
-
var SnapshotAnalysisSchema =
|
|
32925
|
-
state:
|
|
32805
|
+
var AnalysisModeSchema = import_zod8.z.enum(["snapshot", "timeseries"]);
|
|
32806
|
+
var SnapshotAnalysisSchema = import_zod8.z.object({
|
|
32807
|
+
state: import_zod8.z.string().optional().describe(
|
|
32926
32808
|
'State name for the institution roster filter. Example: "North Carolina"'
|
|
32927
32809
|
),
|
|
32928
|
-
certs:
|
|
32810
|
+
certs: import_zod8.z.array(import_zod8.z.number().int().positive()).max(100).optional().describe(
|
|
32929
32811
|
"Optional list of FDIC certificate numbers to compare directly. Max 100."
|
|
32930
32812
|
),
|
|
32931
|
-
institution_filters:
|
|
32813
|
+
institution_filters: import_zod8.z.string().optional().describe(
|
|
32932
32814
|
'Additional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"'
|
|
32933
32815
|
),
|
|
32934
|
-
active_only:
|
|
32935
|
-
start_repdte:
|
|
32816
|
+
active_only: import_zod8.z.boolean().default(true).describe("Limit the comparison set to currently active institutions."),
|
|
32817
|
+
start_repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
|
|
32936
32818
|
"Starting Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Example: 20210331 for Q1 2021. If omitted, defaults to the same quarter one year before end_repdte."
|
|
32937
32819
|
),
|
|
32938
|
-
end_repdte:
|
|
32820
|
+
end_repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
|
|
32939
32821
|
"Ending Report Date (REPDTE) in YYYYMMDD format. Must be a quarter-end date: March 31 (0331), June 30 (0630), September 30 (0930), or December 31 (1231). Must be later than start_repdte. Example: 20251231 for Q4 2025. If omitted, defaults to the most recent quarter-end date with published data (~90-day lag)."
|
|
32940
32822
|
),
|
|
32941
32823
|
analysis_mode: AnalysisModeSchema.default("snapshot").describe(
|
|
32942
32824
|
"Use snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range."
|
|
32943
32825
|
),
|
|
32944
|
-
include_demographics:
|
|
32826
|
+
include_demographics: import_zod8.z.boolean().default(true).describe(
|
|
32945
32827
|
"Include office-count changes from the demographics dataset when available."
|
|
32946
32828
|
),
|
|
32947
|
-
limit:
|
|
32829
|
+
limit: import_zod8.z.number().int().min(1).max(100).default(10).describe("Maximum number of ranked comparisons to return."),
|
|
32948
32830
|
sort_by: SortFieldSchema.default("asset_growth").describe(
|
|
32949
32831
|
"Comparison field used to rank institutions. Valid options: asset_growth, asset_growth_pct, dep_growth, dep_growth_pct, netinc_change, netinc_change_pct, roa_change, roe_change, offices_change, assets_per_office_change, deposits_per_office_change, deposits_to_assets_change."
|
|
32950
32832
|
),
|
|
32951
|
-
sort_order:
|
|
32833
|
+
sort_order: import_zod8.z.enum(["ASC", "DESC"]).default("DESC").describe("Sort direction for the ranked comparisons.")
|
|
32952
32834
|
});
|
|
32953
32835
|
function resolveSnapshotDefaults(params) {
|
|
32954
32836
|
const end_repdte = params.end_repdte ?? getDefaultReportDate();
|
|
@@ -33499,6 +33381,7 @@ Inputs:
|
|
|
33499
33381
|
|
|
33500
33382
|
Returns concise comparison text plus structured deltas, derived metrics, and insight tags for each institution.`,
|
|
33501
33383
|
inputSchema: SnapshotAnalysisSchema,
|
|
33384
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
33502
33385
|
annotations: {
|
|
33503
33386
|
readOnlyHint: true,
|
|
33504
33387
|
destructiveHint: false,
|
|
@@ -33752,7 +33635,7 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
|
|
|
33752
33635
|
}
|
|
33753
33636
|
|
|
33754
33637
|
// src/tools/peerGroup.ts
|
|
33755
|
-
var
|
|
33638
|
+
var import_zod9 = require("zod");
|
|
33756
33639
|
|
|
33757
33640
|
// src/tools/shared/financialMetrics.ts
|
|
33758
33641
|
function safeRatio(numerator, denominator) {
|
|
@@ -33896,33 +33779,33 @@ var METRIC_DEFINITIONS = {
|
|
|
33896
33779
|
label: "Non-Interest Income Share"
|
|
33897
33780
|
}
|
|
33898
33781
|
};
|
|
33899
|
-
var PeerGroupInputSchema =
|
|
33900
|
-
cert:
|
|
33782
|
+
var PeerGroupInputSchema = import_zod9.z.object({
|
|
33783
|
+
cert: import_zod9.z.number().int().positive().optional().describe(
|
|
33901
33784
|
"Subject institution CERT number. When provided, auto-derives peer criteria and ranks this bank against peers."
|
|
33902
33785
|
),
|
|
33903
|
-
repdte:
|
|
33786
|
+
repdte: import_zod9.z.string().regex(/^\d{8}$/).optional().describe(
|
|
33904
33787
|
"Report Date (REPDTE) in YYYYMMDD format. FDIC data is published quarterly on: March 31, June 30, September 30, and December 31. Example: 20231231 for Q4 2023. If omitted, defaults to the most recent quarter-end date likely to have published data (~90-day lag)."
|
|
33905
33788
|
),
|
|
33906
|
-
asset_min:
|
|
33789
|
+
asset_min: import_zod9.z.number().positive().optional().describe(
|
|
33907
33790
|
"Minimum total assets ($thousands) for peer selection. Defaults to 50% of subject's report-date assets when cert is provided."
|
|
33908
33791
|
),
|
|
33909
|
-
asset_max:
|
|
33792
|
+
asset_max: import_zod9.z.number().positive().optional().describe(
|
|
33910
33793
|
"Maximum total assets ($thousands) for peer selection. Defaults to 200% of subject's report-date assets when cert is provided."
|
|
33911
33794
|
),
|
|
33912
|
-
charter_classes:
|
|
33795
|
+
charter_classes: import_zod9.z.array(import_zod9.z.string()).optional().describe(
|
|
33913
33796
|
`Charter class codes to include (e.g., ["N", "SM"]). Defaults to the subject's charter class when cert is provided.`
|
|
33914
33797
|
),
|
|
33915
|
-
state:
|
|
33916
|
-
raw_filter:
|
|
33798
|
+
state: import_zod9.z.string().regex(/^[A-Z]{2}$/).optional().describe('Two-letter state code (e.g., "NC", "TX").'),
|
|
33799
|
+
raw_filter: import_zod9.z.string().optional().describe(
|
|
33917
33800
|
"Advanced: raw ElasticSearch query string appended to peer selection criteria with AND."
|
|
33918
33801
|
),
|
|
33919
|
-
active_only:
|
|
33802
|
+
active_only: import_zod9.z.boolean().default(true).describe(
|
|
33920
33803
|
"Limit to institutions where ACTIVE:1 (currently operating, FDIC-insured)."
|
|
33921
33804
|
),
|
|
33922
|
-
extra_fields:
|
|
33805
|
+
extra_fields: import_zod9.z.array(import_zod9.z.string()).optional().describe(
|
|
33923
33806
|
"Additional FDIC field names to include as raw values in the response. Does not affect peer selection."
|
|
33924
33807
|
),
|
|
33925
|
-
limit:
|
|
33808
|
+
limit: import_zod9.z.number().int().min(1).max(500).default(50).describe(
|
|
33926
33809
|
"Max peer records returned in the response. All matched peers are used for ranking regardless of this limit."
|
|
33927
33810
|
)
|
|
33928
33811
|
});
|
|
@@ -34051,6 +33934,7 @@ Output includes:
|
|
|
34051
33934
|
|
|
34052
33935
|
Override precedence: cert derives defaults, then explicit params override them.`,
|
|
34053
33936
|
inputSchema: PeerGroupInputSchema,
|
|
33937
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
34054
33938
|
annotations: {
|
|
34055
33939
|
readOnlyHint: true,
|
|
34056
33940
|
destructiveHint: false,
|
|
@@ -34387,7 +34271,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
|
|
|
34387
34271
|
}
|
|
34388
34272
|
|
|
34389
34273
|
// src/tools/bankHealth.ts
|
|
34390
|
-
var
|
|
34274
|
+
var import_zod10 = require("zod");
|
|
34391
34275
|
|
|
34392
34276
|
// src/tools/shared/camelsScoring.ts
|
|
34393
34277
|
var SCORING_RULES = {
|
|
@@ -35708,12 +35592,12 @@ function collectRiskSignals(metrics, components, trends) {
|
|
|
35708
35592
|
}
|
|
35709
35593
|
return signals;
|
|
35710
35594
|
}
|
|
35711
|
-
var BankHealthInputSchema =
|
|
35712
|
-
cert:
|
|
35713
|
-
repdte:
|
|
35595
|
+
var BankHealthInputSchema = import_zod10.z.object({
|
|
35596
|
+
cert: import_zod10.z.number().int().positive().describe("FDIC Certificate Number of the institution to analyze."),
|
|
35597
|
+
repdte: import_zod10.z.string().regex(/^\d{8}$/).optional().describe(
|
|
35714
35598
|
"Report Date (YYYYMMDD). Defaults to the most recent quarter likely to have published data."
|
|
35715
35599
|
),
|
|
35716
|
-
quarters:
|
|
35600
|
+
quarters: import_zod10.z.number().int().min(1).max(20).default(8).describe("Number of prior quarters to fetch for trend analysis (default 8).")
|
|
35717
35601
|
});
|
|
35718
35602
|
function registerBankHealthTools(server) {
|
|
35719
35603
|
server.registerTool(
|
|
@@ -35734,6 +35618,7 @@ Output includes:
|
|
|
35734
35618
|
|
|
35735
35619
|
NOTE: Management (M) is omitted from component scoring \u2014 cannot be assessed from public data. Sensitivity (S) uses proxy metrics (NIM trend, securities concentration). This is a public off-site analytical proxy, not an official CAMELS rating.`,
|
|
35736
35620
|
inputSchema: BankHealthInputSchema,
|
|
35621
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
35737
35622
|
annotations: {
|
|
35738
35623
|
readOnlyHint: true,
|
|
35739
35624
|
destructiveHint: false,
|
|
@@ -35875,7 +35760,7 @@ NOTE: Management (M) is omitted from component scoring \u2014 cannot be assessed
|
|
|
35875
35760
|
}
|
|
35876
35761
|
|
|
35877
35762
|
// src/tools/peerHealth.ts
|
|
35878
|
-
var
|
|
35763
|
+
var import_zod11 = require("zod");
|
|
35879
35764
|
|
|
35880
35765
|
// src/tools/shared/peerEngine.ts
|
|
35881
35766
|
function computeMedian2(values) {
|
|
@@ -35949,15 +35834,15 @@ function computePeerStats(subjectValue, peerValues, options) {
|
|
|
35949
35834
|
}
|
|
35950
35835
|
|
|
35951
35836
|
// src/tools/peerHealth.ts
|
|
35952
|
-
var PeerHealthInputSchema =
|
|
35953
|
-
cert:
|
|
35954
|
-
certs:
|
|
35955
|
-
state:
|
|
35956
|
-
asset_min:
|
|
35957
|
-
asset_max:
|
|
35958
|
-
repdte:
|
|
35959
|
-
sort_by:
|
|
35960
|
-
limit:
|
|
35837
|
+
var PeerHealthInputSchema = import_zod11.z.object({
|
|
35838
|
+
cert: import_zod11.z.number().int().positive().optional().describe("Subject institution CERT to highlight in the ranking. Optional."),
|
|
35839
|
+
certs: import_zod11.z.array(import_zod11.z.number().int().positive()).max(50).optional().describe("Explicit list of CERTs to compare (max 50)."),
|
|
35840
|
+
state: import_zod11.z.string().regex(/^[A-Z]{2}$/).optional().describe('Two-letter state code to select all active institutions (e.g., "WY").'),
|
|
35841
|
+
asset_min: import_zod11.z.number().positive().optional().describe("Minimum total assets ($thousands) for peer selection."),
|
|
35842
|
+
asset_max: import_zod11.z.number().positive().optional().describe("Maximum total assets ($thousands) for peer selection."),
|
|
35843
|
+
repdte: import_zod11.z.string().regex(/^\d{8}$/).optional().describe("Report Date (YYYYMMDD). Defaults to the most recent quarter."),
|
|
35844
|
+
sort_by: import_zod11.z.enum(["composite", "capital", "asset_quality", "earnings", "liquidity", "sensitivity"]).default("composite").describe("Sort results by composite or a specific CAMELS component rating."),
|
|
35845
|
+
limit: import_zod11.z.number().int().min(1).max(100).default(25).describe("Max institutions to return in the response.")
|
|
35961
35846
|
});
|
|
35962
35847
|
function sortKeyToComponent(key) {
|
|
35963
35848
|
const map = {
|
|
@@ -35987,6 +35872,7 @@ Output: Ranked list with per-institution proxy_score (1-4 scale) and proxy_band,
|
|
|
35987
35872
|
|
|
35988
35873
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
35989
35874
|
inputSchema: PeerHealthInputSchema,
|
|
35875
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
35990
35876
|
annotations: {
|
|
35991
35877
|
readOnlyHint: true,
|
|
35992
35878
|
destructiveHint: false,
|
|
@@ -36356,7 +36242,7 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36356
36242
|
}
|
|
36357
36243
|
|
|
36358
36244
|
// src/tools/riskSignals.ts
|
|
36359
|
-
var
|
|
36245
|
+
var import_zod12 = require("zod");
|
|
36360
36246
|
function classifyRiskSignals(metrics, trends) {
|
|
36361
36247
|
const signals = [];
|
|
36362
36248
|
if (metrics.tier1_leverage !== null && metrics.tier1_leverage < 5) {
|
|
@@ -36427,15 +36313,15 @@ function classifyRiskSignals(metrics, trends) {
|
|
|
36427
36313
|
return signals;
|
|
36428
36314
|
}
|
|
36429
36315
|
var SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 };
|
|
36430
|
-
var RiskSignalsInputSchema =
|
|
36431
|
-
state:
|
|
36432
|
-
certs:
|
|
36433
|
-
asset_min:
|
|
36434
|
-
asset_max:
|
|
36435
|
-
repdte:
|
|
36436
|
-
min_severity:
|
|
36437
|
-
quarters:
|
|
36438
|
-
limit:
|
|
36316
|
+
var RiskSignalsInputSchema = import_zod12.z.object({
|
|
36317
|
+
state: import_zod12.z.string().regex(/^[A-Z]{2}$/).optional().describe("Scan all active institutions in this state."),
|
|
36318
|
+
certs: import_zod12.z.array(import_zod12.z.number().int().positive()).max(50).optional().describe("Specific CERTs to scan (max 50)."),
|
|
36319
|
+
asset_min: import_zod12.z.number().positive().optional().describe("Minimum total assets ($thousands) filter."),
|
|
36320
|
+
asset_max: import_zod12.z.number().positive().optional().describe("Maximum total assets ($thousands) filter."),
|
|
36321
|
+
repdte: import_zod12.z.string().regex(/^\d{8}$/).optional().describe("Report Date (YYYYMMDD). Defaults to the most recent quarter."),
|
|
36322
|
+
min_severity: import_zod12.z.enum(["info", "warning", "critical"]).default("warning").describe("Minimum severity level to include in results (default: warning)."),
|
|
36323
|
+
quarters: import_zod12.z.number().int().min(1).max(12).default(4).describe("Prior quarters to fetch for trend analysis (default 4)."),
|
|
36324
|
+
limit: import_zod12.z.number().int().min(1).max(100).default(25).describe("Max flagged institutions to return.")
|
|
36439
36325
|
});
|
|
36440
36326
|
function registerRiskSignalTools(server) {
|
|
36441
36327
|
server.registerTool(
|
|
@@ -36458,6 +36344,7 @@ Output: Per-institution risk signals ranked by severity count. The proxy engine
|
|
|
36458
36344
|
|
|
36459
36345
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
36460
36346
|
inputSchema: RiskSignalsInputSchema,
|
|
36347
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
36461
36348
|
annotations: {
|
|
36462
36349
|
readOnlyHint: true,
|
|
36463
36350
|
destructiveHint: false,
|
|
@@ -36677,7 +36564,7 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36677
36564
|
}
|
|
36678
36565
|
|
|
36679
36566
|
// src/tools/creditConcentration.ts
|
|
36680
|
-
var
|
|
36567
|
+
var import_zod13 = require("zod");
|
|
36681
36568
|
|
|
36682
36569
|
// src/tools/shared/creditConcentration.ts
|
|
36683
36570
|
var CREDIT_FIELDS = [
|
|
@@ -36793,9 +36680,9 @@ function formatCreditSummaryText(summary) {
|
|
|
36793
36680
|
}
|
|
36794
36681
|
return parts.join("\n");
|
|
36795
36682
|
}
|
|
36796
|
-
var CreditConcentrationSchema =
|
|
36797
|
-
cert:
|
|
36798
|
-
repdte:
|
|
36683
|
+
var CreditConcentrationSchema = import_zod13.z.object({
|
|
36684
|
+
cert: import_zod13.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
36685
|
+
repdte: import_zod13.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
36799
36686
|
});
|
|
36800
36687
|
function registerCreditConcentrationTools(server) {
|
|
36801
36688
|
server.registerTool(
|
|
@@ -36813,6 +36700,7 @@ Output includes:
|
|
|
36813
36700
|
|
|
36814
36701
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
36815
36702
|
inputSchema: CreditConcentrationSchema,
|
|
36703
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
36816
36704
|
annotations: {
|
|
36817
36705
|
readOnlyHint: true,
|
|
36818
36706
|
destructiveHint: false,
|
|
@@ -36899,7 +36787,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
36899
36787
|
}
|
|
36900
36788
|
|
|
36901
36789
|
// src/tools/fundingProfile.ts
|
|
36902
|
-
var
|
|
36790
|
+
var import_zod14 = require("zod");
|
|
36903
36791
|
|
|
36904
36792
|
// src/tools/shared/fundingProfile.ts
|
|
36905
36793
|
var FUNDING_FIELDS = "CERT,REPDTE,ASSET,DEP,DEPDOM,DEPFOR,COREDEP,BROR,FREPP,EFREPP,EINTEXP,DEPDASTR,CHBALR,LNLSDEPR";
|
|
@@ -36999,9 +36887,9 @@ function formatFundingSummaryText(summary) {
|
|
|
36999
36887
|
}
|
|
37000
36888
|
return parts.join("\n");
|
|
37001
36889
|
}
|
|
37002
|
-
var FundingProfileSchema =
|
|
37003
|
-
cert:
|
|
37004
|
-
repdte:
|
|
36890
|
+
var FundingProfileSchema = import_zod14.z.object({
|
|
36891
|
+
cert: import_zod14.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
36892
|
+
repdte: import_zod14.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
37005
36893
|
});
|
|
37006
36894
|
function registerFundingProfileTools(server) {
|
|
37007
36895
|
server.registerTool(
|
|
@@ -37019,6 +36907,7 @@ Output includes:
|
|
|
37019
36907
|
|
|
37020
36908
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
37021
36909
|
inputSchema: FundingProfileSchema,
|
|
36910
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37022
36911
|
annotations: {
|
|
37023
36912
|
readOnlyHint: true,
|
|
37024
36913
|
destructiveHint: false,
|
|
@@ -37105,7 +36994,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
37105
36994
|
}
|
|
37106
36995
|
|
|
37107
36996
|
// src/tools/securitiesPortfolio.ts
|
|
37108
|
-
var
|
|
36997
|
+
var import_zod15 = require("zod");
|
|
37109
36998
|
|
|
37110
36999
|
// src/tools/shared/securitiesPortfolio.ts
|
|
37111
37000
|
var SECURITIES_FIELDS = "CERT,REPDTE,ASSET,EQTOT,SC,SCRES";
|
|
@@ -37190,9 +37079,9 @@ function formatSecuritiesSummaryText(summary) {
|
|
|
37190
37079
|
}
|
|
37191
37080
|
return parts.join("\n");
|
|
37192
37081
|
}
|
|
37193
|
-
var SecuritiesPortfolioSchema =
|
|
37194
|
-
cert:
|
|
37195
|
-
repdte:
|
|
37082
|
+
var SecuritiesPortfolioSchema = import_zod15.z.object({
|
|
37083
|
+
cert: import_zod15.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37084
|
+
repdte: import_zod15.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
37196
37085
|
});
|
|
37197
37086
|
function registerSecuritiesPortfolioTools(server) {
|
|
37198
37087
|
server.registerTool(
|
|
@@ -37210,6 +37099,7 @@ Output includes:
|
|
|
37210
37099
|
|
|
37211
37100
|
NOTE: This is an analytical tool based on public financial data. AFS/HTM breakdown is not currently available from the FDIC API.`,
|
|
37212
37101
|
inputSchema: SecuritiesPortfolioSchema,
|
|
37102
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37213
37103
|
annotations: {
|
|
37214
37104
|
readOnlyHint: true,
|
|
37215
37105
|
destructiveHint: false,
|
|
@@ -37296,7 +37186,7 @@ NOTE: This is an analytical tool based on public financial data. AFS/HTM breakdo
|
|
|
37296
37186
|
}
|
|
37297
37187
|
|
|
37298
37188
|
// src/tools/ubprAnalysis.ts
|
|
37299
|
-
var
|
|
37189
|
+
var import_zod16 = require("zod");
|
|
37300
37190
|
|
|
37301
37191
|
// src/tools/shared/ubprRatios.ts
|
|
37302
37192
|
var UBPR_FIELDS = "CERT,REPDTE,ASSET,ROA,ROE,ROAPTX,NIMY,EEFFR,INTINC,EINTEXP,NONII,NONIX,NETINC,ELNATRY,LNLSNET,LNRE,LNCI,LNCON,LNAG,DEP,COREDEP,DEPDOM,DEPFOR,BROR,FREPP,IDT1CER,IDT1RWAJR,EQV,EQTOT,LNLSDEPR,DEPDASTR,CHBALR,NPERFV,NCLNLSR,NTLNLSR,LNATRESR,LNRESNCR,SC";
|
|
@@ -37407,9 +37297,9 @@ function formatUbprSummaryText(summary) {
|
|
|
37407
37297
|
parts.push("UBPR-equivalent calculations, not official FFIEC UBPR output.");
|
|
37408
37298
|
return parts.join("\n");
|
|
37409
37299
|
}
|
|
37410
|
-
var UbprAnalysisSchema =
|
|
37411
|
-
cert:
|
|
37412
|
-
repdte:
|
|
37300
|
+
var UbprAnalysisSchema = import_zod16.z.object({
|
|
37301
|
+
cert: import_zod16.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37302
|
+
repdte: import_zod16.z.string().length(8).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
37413
37303
|
});
|
|
37414
37304
|
function registerUbprAnalysisTools(server) {
|
|
37415
37305
|
server.registerTool(
|
|
@@ -37428,6 +37318,7 @@ Output includes:
|
|
|
37428
37318
|
|
|
37429
37319
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
37430
37320
|
inputSchema: UbprAnalysisSchema,
|
|
37321
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37431
37322
|
annotations: {
|
|
37432
37323
|
readOnlyHint: true,
|
|
37433
37324
|
destructiveHint: false,
|
|
@@ -37528,7 +37419,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
37528
37419
|
}
|
|
37529
37420
|
|
|
37530
37421
|
// src/tools/marketShareAnalysis.ts
|
|
37531
|
-
var
|
|
37422
|
+
var import_zod17 = require("zod");
|
|
37532
37423
|
|
|
37533
37424
|
// src/tools/shared/marketShare.ts
|
|
37534
37425
|
function computeMarketShare(branches) {
|
|
@@ -37641,16 +37532,16 @@ function formatMarketShareText(summary) {
|
|
|
37641
37532
|
function getDefaultSodYear() {
|
|
37642
37533
|
return (/* @__PURE__ */ new Date()).getFullYear() - 1;
|
|
37643
37534
|
}
|
|
37644
|
-
var MarketShareInputSchema =
|
|
37645
|
-
msa:
|
|
37535
|
+
var MarketShareInputSchema = import_zod17.z.object({
|
|
37536
|
+
msa: import_zod17.z.number().int().positive().optional().describe(
|
|
37646
37537
|
"FDIC MSABR numeric code for the Metropolitan Statistical Area (e.g., 19100 for Dallas-Fort Worth-Arlington, 42660 for Seattle-Tacoma-Bellevue). Use fdic_search_sod with MSABR to look up codes."
|
|
37647
37538
|
),
|
|
37648
|
-
city:
|
|
37649
|
-
state:
|
|
37539
|
+
city: import_zod17.z.string().optional().describe('City name (e.g., "Austin"). Requires state.'),
|
|
37540
|
+
state: import_zod17.z.string().length(2).optional().describe(
|
|
37650
37541
|
"Two-letter state abbreviation (e.g., TX). Required when using city filter."
|
|
37651
37542
|
),
|
|
37652
|
-
year:
|
|
37653
|
-
cert:
|
|
37543
|
+
year: import_zod17.z.number().int().min(1994).optional().describe("SOD report year (1994-present). Defaults to most recent."),
|
|
37544
|
+
cert: import_zod17.z.number().int().positive().optional().describe("Highlight a specific institution in the results.")
|
|
37654
37545
|
});
|
|
37655
37546
|
function registerMarketShareAnalysisTools(server) {
|
|
37656
37547
|
server.registerTool(
|
|
@@ -37673,6 +37564,7 @@ Output includes:
|
|
|
37673
37564
|
|
|
37674
37565
|
Requires at least one of: msa (numeric MSABR code), or city + state.`,
|
|
37675
37566
|
inputSchema: MarketShareInputSchema,
|
|
37567
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37676
37568
|
annotations: {
|
|
37677
37569
|
readOnlyHint: true,
|
|
37678
37570
|
destructiveHint: false,
|
|
@@ -37788,7 +37680,7 @@ Requires at least one of: msa (numeric MSABR code), or city + state.`,
|
|
|
37788
37680
|
}
|
|
37789
37681
|
|
|
37790
37682
|
// src/tools/franchiseFootprint.ts
|
|
37791
|
-
var
|
|
37683
|
+
var import_zod18 = require("zod");
|
|
37792
37684
|
var SOD_BRANCH_FIELDS = "CERT,NAMEFULL,DEPSUMBR,BRNUM,MSABR,STALPBR,YEAR";
|
|
37793
37685
|
var SOD_FETCH_LIMIT2 = 1e4;
|
|
37794
37686
|
var NON_MSA_LABEL = "Non-MSA / Rural";
|
|
@@ -37827,9 +37719,9 @@ function formatFranchiseFootprintText(summary) {
|
|
|
37827
37719
|
function getDefaultSodYear2() {
|
|
37828
37720
|
return (/* @__PURE__ */ new Date()).getFullYear() - 1;
|
|
37829
37721
|
}
|
|
37830
|
-
var FranchiseFootprintInputSchema =
|
|
37831
|
-
cert:
|
|
37832
|
-
year:
|
|
37722
|
+
var FranchiseFootprintInputSchema = import_zod18.z.object({
|
|
37723
|
+
cert: import_zod18.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37724
|
+
year: import_zod18.z.number().int().min(1994).optional().describe("SOD report year. Defaults to most recent.")
|
|
37833
37725
|
});
|
|
37834
37726
|
function registerFranchiseFootprintTools(server) {
|
|
37835
37727
|
server.registerTool(
|
|
@@ -37847,6 +37739,7 @@ Output includes:
|
|
|
37847
37739
|
|
|
37848
37740
|
Branches outside MSAs are grouped under "Non-MSA / Rural".`,
|
|
37849
37741
|
inputSchema: FranchiseFootprintInputSchema,
|
|
37742
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37850
37743
|
annotations: {
|
|
37851
37744
|
readOnlyHint: true,
|
|
37852
37745
|
destructiveHint: false,
|
|
@@ -37967,7 +37860,7 @@ Branches outside MSAs are grouped under "Non-MSA / Rural".`,
|
|
|
37967
37860
|
}
|
|
37968
37861
|
|
|
37969
37862
|
// src/tools/holdingCompanyProfile.ts
|
|
37970
|
-
var
|
|
37863
|
+
var import_zod19 = require("zod");
|
|
37971
37864
|
|
|
37972
37865
|
// src/tools/shared/holdingCompany.ts
|
|
37973
37866
|
var INDEPENDENT_LABEL = "(Independent)";
|
|
@@ -38084,11 +37977,11 @@ function formatHoldingCompanyProfileText(result) {
|
|
|
38084
37977
|
}
|
|
38085
37978
|
return parts.join("\n");
|
|
38086
37979
|
}
|
|
38087
|
-
var HoldingCompanyProfileSchema =
|
|
38088
|
-
hc_name:
|
|
37980
|
+
var HoldingCompanyProfileSchema = import_zod19.z.object({
|
|
37981
|
+
hc_name: import_zod19.z.string().optional().describe(
|
|
38089
37982
|
'Holding company name (e.g., "JPMORGAN CHASE & CO"). Uses NAMEHCR field.'
|
|
38090
37983
|
),
|
|
38091
|
-
cert:
|
|
37984
|
+
cert: import_zod19.z.number().int().positive().optional().describe(
|
|
38092
37985
|
"CERT of any subsidiary \u2014 looks up its holding company, then profiles the entire HC."
|
|
38093
37986
|
)
|
|
38094
37987
|
});
|
|
@@ -38106,6 +37999,7 @@ Output includes:
|
|
|
38106
37999
|
|
|
38107
38000
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
38108
38001
|
inputSchema: HoldingCompanyProfileSchema,
|
|
38002
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
38109
38003
|
annotations: {
|
|
38110
38004
|
readOnlyHint: true,
|
|
38111
38005
|
destructiveHint: false,
|
|
@@ -38276,7 +38170,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
38276
38170
|
}
|
|
38277
38171
|
|
|
38278
38172
|
// src/tools/regionalContext.ts
|
|
38279
|
-
var
|
|
38173
|
+
var import_zod20 = require("zod");
|
|
38280
38174
|
|
|
38281
38175
|
// src/services/fredClient.ts
|
|
38282
38176
|
var import_axios2 = __toESM(require("axios"));
|
|
@@ -38476,14 +38370,14 @@ function formatRegionalContextText(summary) {
|
|
|
38476
38370
|
parts.push("Note: Economic data from FRED (Federal Reserve Economic Data).");
|
|
38477
38371
|
return parts.join("\n");
|
|
38478
38372
|
}
|
|
38479
|
-
var RegionalContextSchema =
|
|
38480
|
-
cert:
|
|
38373
|
+
var RegionalContextSchema = import_zod20.z.object({
|
|
38374
|
+
cert: import_zod20.z.number().int().positive().optional().describe(
|
|
38481
38375
|
"FDIC Certificate Number \u2014 auto-detects state from institution record."
|
|
38482
38376
|
),
|
|
38483
|
-
state:
|
|
38377
|
+
state: import_zod20.z.string().length(2).optional().describe(
|
|
38484
38378
|
"Two-letter state abbreviation (e.g., TX). Alternative to cert-based lookup."
|
|
38485
38379
|
),
|
|
38486
|
-
repdte:
|
|
38380
|
+
repdte: import_zod20.z.string().regex(/^\d{8}$/).optional().describe(
|
|
38487
38381
|
"Reference report date (YYYYMMDD). FRED data fetched for 2 years before this date."
|
|
38488
38382
|
)
|
|
38489
38383
|
});
|
|
@@ -38502,6 +38396,7 @@ Output includes:
|
|
|
38502
38396
|
|
|
38503
38397
|
NOTE: Requires FRED_API_KEY environment variable for reliable data access. Degrades gracefully without it.`,
|
|
38504
38398
|
inputSchema: RegionalContextSchema,
|
|
38399
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
38505
38400
|
annotations: {
|
|
38506
38401
|
readOnlyHint: true,
|
|
38507
38402
|
destructiveHint: false,
|
|
@@ -38639,6 +38534,909 @@ NOTE: Requires FRED_API_KEY environment variable for reliable data access. Degra
|
|
|
38639
38534
|
);
|
|
38640
38535
|
}
|
|
38641
38536
|
|
|
38537
|
+
// src/tools/chatgptRetrieval.ts
|
|
38538
|
+
var import_zod21 = require("zod");
|
|
38539
|
+
|
|
38540
|
+
// src/tools/shared/chatgptUrls.ts
|
|
38541
|
+
var FDIC_BANKFIND_BASE_URL = "https://banks.data.fdic.gov/bankfind-suite";
|
|
38542
|
+
var FDIC_FAILED_BANK_LIST_URL = "https://www.fdic.gov/bank-failures/failed-bank-list";
|
|
38543
|
+
var PROJECT_TOOL_REFERENCE_URL = "https://jflamb.github.io/fdic-mcp-server/tool-reference/";
|
|
38544
|
+
function getInstitutionUrl(cert) {
|
|
38545
|
+
return `${FDIC_BANKFIND_BASE_URL}/bankfind/details/${cert}`;
|
|
38546
|
+
}
|
|
38547
|
+
function getFailedBankListUrl() {
|
|
38548
|
+
return FDIC_FAILED_BANK_LIST_URL;
|
|
38549
|
+
}
|
|
38550
|
+
function getSchemaDocsUrl(endpoint) {
|
|
38551
|
+
return `${PROJECT_TOOL_REFERENCE_URL}#${encodeURIComponent(endpoint)}`;
|
|
38552
|
+
}
|
|
38553
|
+
function getBranchCitationUrl() {
|
|
38554
|
+
return `${PROJECT_TOOL_REFERENCE_URL}#fdic_search_locations`;
|
|
38555
|
+
}
|
|
38556
|
+
|
|
38557
|
+
// src/tools/chatgptRetrieval.ts
|
|
38558
|
+
var SearchInputSchema = import_zod21.z.object({
|
|
38559
|
+
query: import_zod21.z.string().min(1).describe("Natural-language search query.")
|
|
38560
|
+
});
|
|
38561
|
+
var FetchInputSchema = import_zod21.z.object({
|
|
38562
|
+
id: import_zod21.z.string().min(1).describe(
|
|
38563
|
+
"Retrieval item id, such as institution:<CERT>, failure:<CERT>, branch:<UNINUM>, or schema:<endpoint>."
|
|
38564
|
+
)
|
|
38565
|
+
});
|
|
38566
|
+
var MAX_SEARCH_RESULTS = 8;
|
|
38567
|
+
function asString(value) {
|
|
38568
|
+
if (value === void 0 || value === null) {
|
|
38569
|
+
return "";
|
|
38570
|
+
}
|
|
38571
|
+
return String(value);
|
|
38572
|
+
}
|
|
38573
|
+
function asNumber2(value) {
|
|
38574
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
38575
|
+
return value;
|
|
38576
|
+
}
|
|
38577
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
38578
|
+
const parsed = Number(value);
|
|
38579
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
38580
|
+
}
|
|
38581
|
+
return void 0;
|
|
38582
|
+
}
|
|
38583
|
+
function jsonText(payload) {
|
|
38584
|
+
return JSON.stringify(payload);
|
|
38585
|
+
}
|
|
38586
|
+
function escapeFilterValue(value) {
|
|
38587
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').trim();
|
|
38588
|
+
}
|
|
38589
|
+
function normalizeQuery(query) {
|
|
38590
|
+
return query.replace(/\s+/g, " ").trim();
|
|
38591
|
+
}
|
|
38592
|
+
function extractCertLikeNumber(query) {
|
|
38593
|
+
const match = query.match(/\b(?:cert(?:ificate)?\s*#?\s*)?(\d{1,7})\b/i);
|
|
38594
|
+
if (!match) {
|
|
38595
|
+
return void 0;
|
|
38596
|
+
}
|
|
38597
|
+
return Number.parseInt(match[1], 10);
|
|
38598
|
+
}
|
|
38599
|
+
function shouldSearchFailures(query) {
|
|
38600
|
+
return /\b(fail|failed|failure|closed|resolution|receivership)\b/i.test(
|
|
38601
|
+
query
|
|
38602
|
+
);
|
|
38603
|
+
}
|
|
38604
|
+
function shouldSearchBranches(query) {
|
|
38605
|
+
return /\b(branch|branches|office|offices|location|locations|address|city|county|zip|market|msa)\b/i.test(
|
|
38606
|
+
query
|
|
38607
|
+
);
|
|
38608
|
+
}
|
|
38609
|
+
function shouldSearchSchemas(query) {
|
|
38610
|
+
return /\b(schema|field|fields|column|columns|endpoint|call report|financials?|sod|summary of deposits|demographics|metadata)\b/i.test(
|
|
38611
|
+
query
|
|
38612
|
+
);
|
|
38613
|
+
}
|
|
38614
|
+
function getRecordTitle(record, fallback) {
|
|
38615
|
+
return asString(record.NAME) || asString(record.UNINAME) || asString(record.NAMEFULL) || asString(record.OFFNAME) || fallback;
|
|
38616
|
+
}
|
|
38617
|
+
function formatLocation(record) {
|
|
38618
|
+
return [record.CITY, record.STALP].map(asString).filter(Boolean).join(", ");
|
|
38619
|
+
}
|
|
38620
|
+
function buildBranchId(record) {
|
|
38621
|
+
const uninum = asString(record.UNINUM);
|
|
38622
|
+
if (uninum) {
|
|
38623
|
+
return `branch:${encodeURIComponent(uninum)}`;
|
|
38624
|
+
}
|
|
38625
|
+
const cert = asString(record.CERT);
|
|
38626
|
+
const brnum = asString(record.BRNUM);
|
|
38627
|
+
const zip = asString(record.ZIP);
|
|
38628
|
+
if (!cert || !brnum) {
|
|
38629
|
+
return void 0;
|
|
38630
|
+
}
|
|
38631
|
+
return `branch:${encodeURIComponent([cert, brnum, zip].join("~"))}`;
|
|
38632
|
+
}
|
|
38633
|
+
function institutionSearchResult(record) {
|
|
38634
|
+
const cert = asNumber2(record.CERT);
|
|
38635
|
+
if (cert === void 0) {
|
|
38636
|
+
return void 0;
|
|
38637
|
+
}
|
|
38638
|
+
const title = getRecordTitle(record, `FDIC institution ${cert}`);
|
|
38639
|
+
const location = formatLocation(record);
|
|
38640
|
+
return {
|
|
38641
|
+
id: `institution:${cert}`,
|
|
38642
|
+
title: location ? `${title} (${location})` : title,
|
|
38643
|
+
url: getInstitutionUrl(cert)
|
|
38644
|
+
};
|
|
38645
|
+
}
|
|
38646
|
+
function failureSearchResult(record) {
|
|
38647
|
+
const cert = asNumber2(record.CERT);
|
|
38648
|
+
if (cert === void 0) {
|
|
38649
|
+
return void 0;
|
|
38650
|
+
}
|
|
38651
|
+
const title = getRecordTitle(record, `Failed bank ${cert}`);
|
|
38652
|
+
const failDate = asString(record.FAILDATE);
|
|
38653
|
+
return {
|
|
38654
|
+
id: `failure:${cert}`,
|
|
38655
|
+
title: failDate ? `${title} failed ${failDate}` : `${title} failure record`,
|
|
38656
|
+
url: getFailedBankListUrl()
|
|
38657
|
+
};
|
|
38658
|
+
}
|
|
38659
|
+
function branchSearchResult(record) {
|
|
38660
|
+
const id = buildBranchId(record);
|
|
38661
|
+
if (!id) {
|
|
38662
|
+
return void 0;
|
|
38663
|
+
}
|
|
38664
|
+
const name = getRecordTitle(record, "FDIC branch location");
|
|
38665
|
+
const address = [record.ADDRESS, record.CITY, record.STALP, record.ZIP].map(asString).filter(Boolean).join(", ");
|
|
38666
|
+
return {
|
|
38667
|
+
id,
|
|
38668
|
+
title: address ? `${name} - ${address}` : name,
|
|
38669
|
+
url: getBranchCitationUrl()
|
|
38670
|
+
};
|
|
38671
|
+
}
|
|
38672
|
+
function schemaSearchResults(query) {
|
|
38673
|
+
const normalized = normalizeQuery(query).toLowerCase();
|
|
38674
|
+
const metadata = listEndpointMetadata();
|
|
38675
|
+
return metadata.filter((endpoint) => {
|
|
38676
|
+
if (normalized.includes(endpoint.endpoint.toLowerCase())) {
|
|
38677
|
+
return true;
|
|
38678
|
+
}
|
|
38679
|
+
if (endpoint.title.toLowerCase().includes(normalized)) {
|
|
38680
|
+
return true;
|
|
38681
|
+
}
|
|
38682
|
+
return Object.keys(endpoint.fields).some(
|
|
38683
|
+
(field) => normalized.includes(field.toLowerCase())
|
|
38684
|
+
);
|
|
38685
|
+
}).slice(0, 2).map((endpoint) => ({
|
|
38686
|
+
id: `schema:${endpoint.endpoint}`,
|
|
38687
|
+
title: `${endpoint.title} schema`,
|
|
38688
|
+
url: getSchemaDocsUrl(endpoint.endpoint)
|
|
38689
|
+
}));
|
|
38690
|
+
}
|
|
38691
|
+
async function searchInstitutions(query) {
|
|
38692
|
+
const cert = extractCertLikeNumber(query);
|
|
38693
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : `NAME:"${escapeFilterValue(query)}"`;
|
|
38694
|
+
const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
38695
|
+
filters,
|
|
38696
|
+
fields: "CERT,NAME,CITY,STALP,ACTIVE",
|
|
38697
|
+
limit: 3,
|
|
38698
|
+
sort_by: "ACTIVE",
|
|
38699
|
+
sort_order: "DESC"
|
|
38700
|
+
});
|
|
38701
|
+
return extractRecords(response).map(institutionSearchResult).filter((result) => result !== void 0);
|
|
38702
|
+
}
|
|
38703
|
+
async function searchFailures(query) {
|
|
38704
|
+
const cert = extractCertLikeNumber(query);
|
|
38705
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : `NAME:"${escapeFilterValue(query)}"`;
|
|
38706
|
+
const response = await queryEndpoint(ENDPOINTS.FAILURES, {
|
|
38707
|
+
filters,
|
|
38708
|
+
fields: "CERT,NAME,CITY,STALP,FAILDATE,RESTYPE",
|
|
38709
|
+
limit: 2,
|
|
38710
|
+
sort_by: "FAILDATE",
|
|
38711
|
+
sort_order: "DESC"
|
|
38712
|
+
});
|
|
38713
|
+
return extractRecords(response).map(failureSearchResult).filter((result) => result !== void 0);
|
|
38714
|
+
}
|
|
38715
|
+
async function searchBranches(query) {
|
|
38716
|
+
const cert = extractCertLikeNumber(query);
|
|
38717
|
+
const normalized = normalizeQuery(query);
|
|
38718
|
+
const zip = normalized.match(/\b\d{5}\b/)?.[0];
|
|
38719
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : zip !== void 0 ? `ZIP:${zip}` : `CITY:"${escapeFilterValue(normalized)}"`;
|
|
38720
|
+
const response = await queryEndpoint(ENDPOINTS.LOCATIONS, {
|
|
38721
|
+
filters,
|
|
38722
|
+
fields: "UNINUM,CERT,NAME,OFFNAME,ADDRESS,CITY,STALP,ZIP",
|
|
38723
|
+
limit: 3,
|
|
38724
|
+
sort_by: "UNINUM",
|
|
38725
|
+
sort_order: "ASC"
|
|
38726
|
+
});
|
|
38727
|
+
return extractRecords(response).map(branchSearchResult).filter((result) => result !== void 0);
|
|
38728
|
+
}
|
|
38729
|
+
async function safeSearch(searcher) {
|
|
38730
|
+
try {
|
|
38731
|
+
return await searcher();
|
|
38732
|
+
} catch {
|
|
38733
|
+
return [];
|
|
38734
|
+
}
|
|
38735
|
+
}
|
|
38736
|
+
function dedupeResults(results) {
|
|
38737
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38738
|
+
const deduped = [];
|
|
38739
|
+
for (const result of results) {
|
|
38740
|
+
if (seen.has(result.id)) {
|
|
38741
|
+
continue;
|
|
38742
|
+
}
|
|
38743
|
+
seen.add(result.id);
|
|
38744
|
+
deduped.push(result);
|
|
38745
|
+
}
|
|
38746
|
+
return deduped.slice(0, MAX_SEARCH_RESULTS);
|
|
38747
|
+
}
|
|
38748
|
+
function recordText(record, fields) {
|
|
38749
|
+
return fields.map((field) => [field, asString(record[field])]).filter(([, value]) => value !== "").map(([field, value]) => `${field}: ${value}`).join("\n");
|
|
38750
|
+
}
|
|
38751
|
+
async function fetchInstitution(cert) {
|
|
38752
|
+
const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
38753
|
+
filters: `CERT:${cert}`,
|
|
38754
|
+
limit: 1
|
|
38755
|
+
});
|
|
38756
|
+
const record = extractRecords(response)[0];
|
|
38757
|
+
if (!record) {
|
|
38758
|
+
throw new Error(`No institution found for CERT ${cert}.`);
|
|
38759
|
+
}
|
|
38760
|
+
const title = getRecordTitle(record, `FDIC institution ${cert}`);
|
|
38761
|
+
return {
|
|
38762
|
+
id: `institution:${cert}`,
|
|
38763
|
+
title,
|
|
38764
|
+
text: recordText(record, [
|
|
38765
|
+
"CERT",
|
|
38766
|
+
"NAME",
|
|
38767
|
+
"CITY",
|
|
38768
|
+
"STALP",
|
|
38769
|
+
"STNAME",
|
|
38770
|
+
"ACTIVE",
|
|
38771
|
+
"ASSET",
|
|
38772
|
+
"DEP",
|
|
38773
|
+
"OFFICES",
|
|
38774
|
+
"BKCLASS",
|
|
38775
|
+
"REGAGNT",
|
|
38776
|
+
"ESTYMD"
|
|
38777
|
+
]),
|
|
38778
|
+
url: getInstitutionUrl(cert),
|
|
38779
|
+
metadata: { type: "institution", cert, source: "FDIC BankFind Suite" }
|
|
38780
|
+
};
|
|
38781
|
+
}
|
|
38782
|
+
async function fetchFailure(cert) {
|
|
38783
|
+
const response = await queryEndpoint(ENDPOINTS.FAILURES, {
|
|
38784
|
+
filters: `CERT:${cert}`,
|
|
38785
|
+
limit: 1
|
|
38786
|
+
});
|
|
38787
|
+
const record = extractRecords(response)[0];
|
|
38788
|
+
if (!record) {
|
|
38789
|
+
throw new Error(`No failure record found for CERT ${cert}.`);
|
|
38790
|
+
}
|
|
38791
|
+
const title = getRecordTitle(record, `Failed bank ${cert}`);
|
|
38792
|
+
return {
|
|
38793
|
+
id: `failure:${cert}`,
|
|
38794
|
+
title,
|
|
38795
|
+
text: recordText(record, [
|
|
38796
|
+
"CERT",
|
|
38797
|
+
"NAME",
|
|
38798
|
+
"CITY",
|
|
38799
|
+
"STALP",
|
|
38800
|
+
"FAILDATE",
|
|
38801
|
+
"RESTYPE",
|
|
38802
|
+
"QBFASSET",
|
|
38803
|
+
"COST"
|
|
38804
|
+
]),
|
|
38805
|
+
url: getFailedBankListUrl(),
|
|
38806
|
+
metadata: { type: "failure", cert, source: "FDIC Failed Bank List" }
|
|
38807
|
+
};
|
|
38808
|
+
}
|
|
38809
|
+
async function fetchBranch(rawId) {
|
|
38810
|
+
const decoded = decodeURIComponent(rawId);
|
|
38811
|
+
const [cert, brnum, zip] = decoded.split("~");
|
|
38812
|
+
const filters = cert && brnum ? [`CERT:${cert}`, `BRNUM:${brnum}`, zip ? `ZIP:${zip}` : void 0].filter(Boolean).join(" AND ") : `UNINUM:${decoded}`;
|
|
38813
|
+
const response = await queryEndpoint(ENDPOINTS.LOCATIONS, {
|
|
38814
|
+
filters,
|
|
38815
|
+
limit: 1
|
|
38816
|
+
});
|
|
38817
|
+
const record = extractRecords(response)[0];
|
|
38818
|
+
if (!record) {
|
|
38819
|
+
throw new Error(`No branch/location found for id ${rawId}.`);
|
|
38820
|
+
}
|
|
38821
|
+
const title = getRecordTitle(record, `FDIC branch ${rawId}`);
|
|
38822
|
+
const id = buildBranchId(record) ?? `branch:${rawId}`;
|
|
38823
|
+
return {
|
|
38824
|
+
id,
|
|
38825
|
+
title,
|
|
38826
|
+
text: recordText(record, [
|
|
38827
|
+
"UNINUM",
|
|
38828
|
+
"CERT",
|
|
38829
|
+
"UNINAME",
|
|
38830
|
+
"NAMEFULL",
|
|
38831
|
+
"ADDRESS",
|
|
38832
|
+
"CITY",
|
|
38833
|
+
"STALP",
|
|
38834
|
+
"ZIP",
|
|
38835
|
+
"COUNTY",
|
|
38836
|
+
"BRNUM",
|
|
38837
|
+
"BRSERTYP",
|
|
38838
|
+
"ESTYMD",
|
|
38839
|
+
"ENDEFYMD"
|
|
38840
|
+
]),
|
|
38841
|
+
url: getBranchCitationUrl(),
|
|
38842
|
+
metadata: {
|
|
38843
|
+
type: "branch",
|
|
38844
|
+
cert: record.CERT,
|
|
38845
|
+
uninum: record.UNINUM,
|
|
38846
|
+
source: "FDIC BankFind Suite locations"
|
|
38847
|
+
}
|
|
38848
|
+
};
|
|
38849
|
+
}
|
|
38850
|
+
function fetchSchema(endpoint) {
|
|
38851
|
+
const metadata = getEndpointMetadata(endpoint);
|
|
38852
|
+
if (!metadata) {
|
|
38853
|
+
throw new Error(`No schema metadata found for endpoint ${endpoint}.`);
|
|
38854
|
+
}
|
|
38855
|
+
const fields = Object.values(metadata.fields).slice(0, 200).map((field) => {
|
|
38856
|
+
const title = field.title ? ` - ${field.title}` : "";
|
|
38857
|
+
return `${field.name}${title}`;
|
|
38858
|
+
}).join("\n");
|
|
38859
|
+
return {
|
|
38860
|
+
id: `schema:${endpoint}`,
|
|
38861
|
+
title: `${metadata.title} schema`,
|
|
38862
|
+
text: [
|
|
38863
|
+
metadata.description ?? metadata.title,
|
|
38864
|
+
"",
|
|
38865
|
+
`Endpoint: ${metadata.endpoint}`,
|
|
38866
|
+
`Source: ${metadata.source.docsBaseUrl}`,
|
|
38867
|
+
"",
|
|
38868
|
+
"Fields:",
|
|
38869
|
+
fields
|
|
38870
|
+
].join("\n"),
|
|
38871
|
+
url: getSchemaDocsUrl(endpoint),
|
|
38872
|
+
metadata: {
|
|
38873
|
+
type: "schema",
|
|
38874
|
+
endpoint,
|
|
38875
|
+
field_count: Object.keys(metadata.fields).length,
|
|
38876
|
+
source: metadata.source.docsBaseUrl
|
|
38877
|
+
}
|
|
38878
|
+
};
|
|
38879
|
+
}
|
|
38880
|
+
async function fetchById(id) {
|
|
38881
|
+
const [kind, rawValue] = id.split(":", 2);
|
|
38882
|
+
if (!kind || !rawValue) {
|
|
38883
|
+
throw new Error(
|
|
38884
|
+
"Invalid fetch id. Expected institution:<CERT>, failure:<CERT>, branch:<id>, or schema:<endpoint>."
|
|
38885
|
+
);
|
|
38886
|
+
}
|
|
38887
|
+
if (kind === "institution") {
|
|
38888
|
+
const cert = Number.parseInt(rawValue, 10);
|
|
38889
|
+
if (!Number.isInteger(cert) || cert <= 0) {
|
|
38890
|
+
throw new Error(`Invalid institution CERT in id ${id}.`);
|
|
38891
|
+
}
|
|
38892
|
+
return fetchInstitution(cert);
|
|
38893
|
+
}
|
|
38894
|
+
if (kind === "failure") {
|
|
38895
|
+
const cert = Number.parseInt(rawValue, 10);
|
|
38896
|
+
if (!Number.isInteger(cert) || cert <= 0) {
|
|
38897
|
+
throw new Error(`Invalid failure CERT in id ${id}.`);
|
|
38898
|
+
}
|
|
38899
|
+
return fetchFailure(cert);
|
|
38900
|
+
}
|
|
38901
|
+
if (kind === "branch") {
|
|
38902
|
+
return fetchBranch(rawValue);
|
|
38903
|
+
}
|
|
38904
|
+
if (kind === "schema") {
|
|
38905
|
+
return fetchSchema(rawValue);
|
|
38906
|
+
}
|
|
38907
|
+
throw new Error(`Unsupported fetch id kind: ${kind}.`);
|
|
38908
|
+
}
|
|
38909
|
+
async function runSearch(query) {
|
|
38910
|
+
const normalized = normalizeQuery(query);
|
|
38911
|
+
const shouldIncludeFailures = shouldSearchFailures(normalized);
|
|
38912
|
+
const shouldIncludeBranches = shouldSearchBranches(normalized);
|
|
38913
|
+
const shouldIncludeSchemas = shouldSearchSchemas(normalized);
|
|
38914
|
+
const [institutions, failures, branches] = await Promise.all([
|
|
38915
|
+
safeSearch(() => searchInstitutions(normalized)),
|
|
38916
|
+
shouldIncludeFailures ? safeSearch(() => searchFailures(normalized)) : Promise.resolve([]),
|
|
38917
|
+
shouldIncludeBranches ? safeSearch(() => searchBranches(normalized)) : Promise.resolve([])
|
|
38918
|
+
]);
|
|
38919
|
+
const fallbackFailures = failures.length === 0 && institutions.length === 0 ? await safeSearch(() => searchFailures(normalized)) : [];
|
|
38920
|
+
const fallbackBranches = branches.length === 0 && institutions.length === 0 ? await safeSearch(() => searchBranches(normalized)) : [];
|
|
38921
|
+
const schemas = shouldIncludeSchemas ? schemaSearchResults(normalized) : [];
|
|
38922
|
+
const results = dedupeResults([
|
|
38923
|
+
...institutions,
|
|
38924
|
+
...failures,
|
|
38925
|
+
...branches,
|
|
38926
|
+
...fallbackFailures,
|
|
38927
|
+
...fallbackBranches,
|
|
38928
|
+
...schemas
|
|
38929
|
+
]);
|
|
38930
|
+
return { results };
|
|
38931
|
+
}
|
|
38932
|
+
var SEARCH_DESCRIPTION = "Use this when the model needs citation-friendly FDIC BankFind search results for institutions, failed banks, branches, or schema documentation. Returns up to 8 results with id, title, and source URL.";
|
|
38933
|
+
var FETCH_DESCRIPTION = "Use this when the model needs the full citation text for a result returned by search. Pass the search result id (e.g. 'institution:3511', 'failure:1234', 'branch:<UNINUM>', 'schema:institutions').";
|
|
38934
|
+
function registerSearchTool(server, name) {
|
|
38935
|
+
server.registerTool(
|
|
38936
|
+
name,
|
|
38937
|
+
{
|
|
38938
|
+
title: "Search FDIC BankFind",
|
|
38939
|
+
description: SEARCH_DESCRIPTION,
|
|
38940
|
+
inputSchema: SearchInputSchema,
|
|
38941
|
+
outputSchema: ChatGptSearchResultSchema,
|
|
38942
|
+
annotations: {
|
|
38943
|
+
readOnlyHint: true,
|
|
38944
|
+
destructiveHint: false,
|
|
38945
|
+
idempotentHint: true,
|
|
38946
|
+
openWorldHint: true
|
|
38947
|
+
}
|
|
38948
|
+
},
|
|
38949
|
+
async ({ query }) => {
|
|
38950
|
+
const payload = await runSearch(query);
|
|
38951
|
+
return {
|
|
38952
|
+
content: [{ type: "text", text: jsonText(payload) }],
|
|
38953
|
+
structuredContent: payload
|
|
38954
|
+
};
|
|
38955
|
+
}
|
|
38956
|
+
);
|
|
38957
|
+
}
|
|
38958
|
+
function registerFetchTool(server, name) {
|
|
38959
|
+
server.registerTool(
|
|
38960
|
+
name,
|
|
38961
|
+
{
|
|
38962
|
+
title: "Fetch FDIC BankFind Result",
|
|
38963
|
+
description: FETCH_DESCRIPTION,
|
|
38964
|
+
inputSchema: FetchInputSchema,
|
|
38965
|
+
outputSchema: ChatGptFetchResultSchema,
|
|
38966
|
+
annotations: {
|
|
38967
|
+
readOnlyHint: true,
|
|
38968
|
+
destructiveHint: false,
|
|
38969
|
+
idempotentHint: true,
|
|
38970
|
+
openWorldHint: true
|
|
38971
|
+
}
|
|
38972
|
+
},
|
|
38973
|
+
async ({ id }) => {
|
|
38974
|
+
try {
|
|
38975
|
+
const result = await fetchById(id);
|
|
38976
|
+
return {
|
|
38977
|
+
content: [{ type: "text", text: jsonText(result) }],
|
|
38978
|
+
structuredContent: result
|
|
38979
|
+
};
|
|
38980
|
+
} catch (err) {
|
|
38981
|
+
return formatToolError(err);
|
|
38982
|
+
}
|
|
38983
|
+
}
|
|
38984
|
+
);
|
|
38985
|
+
}
|
|
38986
|
+
function registerChatGptRetrievalTools(server, options = {}) {
|
|
38987
|
+
const includeCanonical = options.includeCanonicalNames ?? true;
|
|
38988
|
+
const includeAliases = options.includeNamespacedAliases ?? true;
|
|
38989
|
+
if (includeCanonical) {
|
|
38990
|
+
registerSearchTool(server, "search");
|
|
38991
|
+
registerFetchTool(server, "fetch");
|
|
38992
|
+
}
|
|
38993
|
+
if (includeAliases) {
|
|
38994
|
+
registerSearchTool(server, "fdic_search");
|
|
38995
|
+
registerFetchTool(server, "fdic_fetch");
|
|
38996
|
+
}
|
|
38997
|
+
}
|
|
38998
|
+
|
|
38999
|
+
// src/tools/chatgptBankDeepDive.ts
|
|
39000
|
+
var import_zod22 = require("zod");
|
|
39001
|
+
|
|
39002
|
+
// src/resources/chatgptAppResources.ts
|
|
39003
|
+
var BANK_DEEP_DIVE_WIDGET_URI = "ui://widget/fdic-bank-deep-dive-v1.html";
|
|
39004
|
+
var MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
|
|
39005
|
+
var BANK_DEEP_DIVE_WIDGET_HTML = String.raw`
|
|
39006
|
+
<div id="root" class="fdic-app">
|
|
39007
|
+
<section class="empty">Loading bank dashboard...</section>
|
|
39008
|
+
</div>
|
|
39009
|
+
<style>
|
|
39010
|
+
:root {
|
|
39011
|
+
color-scheme: light dark;
|
|
39012
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
39013
|
+
}
|
|
39014
|
+
|
|
39015
|
+
body {
|
|
39016
|
+
margin: 0;
|
|
39017
|
+
background: Canvas;
|
|
39018
|
+
color: CanvasText;
|
|
39019
|
+
}
|
|
39020
|
+
|
|
39021
|
+
.fdic-app {
|
|
39022
|
+
box-sizing: border-box;
|
|
39023
|
+
min-height: 100%;
|
|
39024
|
+
padding: 16px;
|
|
39025
|
+
}
|
|
39026
|
+
|
|
39027
|
+
.empty,
|
|
39028
|
+
.panel {
|
|
39029
|
+
border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
|
|
39030
|
+
border-radius: 8px;
|
|
39031
|
+
padding: 14px;
|
|
39032
|
+
}
|
|
39033
|
+
|
|
39034
|
+
.header {
|
|
39035
|
+
display: grid;
|
|
39036
|
+
gap: 6px;
|
|
39037
|
+
margin-bottom: 14px;
|
|
39038
|
+
}
|
|
39039
|
+
|
|
39040
|
+
.eyebrow {
|
|
39041
|
+
color: color-mix(in srgb, CanvasText 58%, transparent);
|
|
39042
|
+
font-size: 12px;
|
|
39043
|
+
font-weight: 650;
|
|
39044
|
+
text-transform: uppercase;
|
|
39045
|
+
}
|
|
39046
|
+
|
|
39047
|
+
h1,
|
|
39048
|
+
h2,
|
|
39049
|
+
p {
|
|
39050
|
+
margin: 0;
|
|
39051
|
+
}
|
|
39052
|
+
|
|
39053
|
+
h1 {
|
|
39054
|
+
font-size: 20px;
|
|
39055
|
+
line-height: 1.25;
|
|
39056
|
+
letter-spacing: 0;
|
|
39057
|
+
}
|
|
39058
|
+
|
|
39059
|
+
h2 {
|
|
39060
|
+
font-size: 14px;
|
|
39061
|
+
line-height: 1.35;
|
|
39062
|
+
letter-spacing: 0;
|
|
39063
|
+
}
|
|
39064
|
+
|
|
39065
|
+
.subtle {
|
|
39066
|
+
color: color-mix(in srgb, CanvasText 64%, transparent);
|
|
39067
|
+
font-size: 13px;
|
|
39068
|
+
}
|
|
39069
|
+
|
|
39070
|
+
.grid {
|
|
39071
|
+
display: grid;
|
|
39072
|
+
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
|
39073
|
+
gap: 8px;
|
|
39074
|
+
margin: 12px 0;
|
|
39075
|
+
}
|
|
39076
|
+
|
|
39077
|
+
.metric {
|
|
39078
|
+
border: 1px solid color-mix(in srgb, CanvasText 12%, transparent);
|
|
39079
|
+
border-radius: 8px;
|
|
39080
|
+
padding: 10px;
|
|
39081
|
+
}
|
|
39082
|
+
|
|
39083
|
+
.metric dt {
|
|
39084
|
+
color: color-mix(in srgb, CanvasText 62%, transparent);
|
|
39085
|
+
font-size: 12px;
|
|
39086
|
+
margin: 0 0 6px;
|
|
39087
|
+
}
|
|
39088
|
+
|
|
39089
|
+
.metric dd {
|
|
39090
|
+
font-size: 16px;
|
|
39091
|
+
font-weight: 680;
|
|
39092
|
+
margin: 0;
|
|
39093
|
+
}
|
|
39094
|
+
|
|
39095
|
+
.section {
|
|
39096
|
+
margin-top: 14px;
|
|
39097
|
+
}
|
|
39098
|
+
|
|
39099
|
+
.list {
|
|
39100
|
+
display: grid;
|
|
39101
|
+
gap: 6px;
|
|
39102
|
+
margin: 8px 0 0;
|
|
39103
|
+
padding: 0;
|
|
39104
|
+
list-style: none;
|
|
39105
|
+
}
|
|
39106
|
+
|
|
39107
|
+
.list li {
|
|
39108
|
+
border-left: 3px solid color-mix(in srgb, CanvasText 24%, transparent);
|
|
39109
|
+
padding: 4px 0 4px 8px;
|
|
39110
|
+
font-size: 13px;
|
|
39111
|
+
}
|
|
39112
|
+
|
|
39113
|
+
.actions {
|
|
39114
|
+
display: flex;
|
|
39115
|
+
flex-wrap: wrap;
|
|
39116
|
+
gap: 8px;
|
|
39117
|
+
margin-top: 14px;
|
|
39118
|
+
}
|
|
39119
|
+
|
|
39120
|
+
button {
|
|
39121
|
+
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
|
|
39122
|
+
border-radius: 8px;
|
|
39123
|
+
background: Canvas;
|
|
39124
|
+
color: CanvasText;
|
|
39125
|
+
cursor: pointer;
|
|
39126
|
+
font: inherit;
|
|
39127
|
+
font-size: 13px;
|
|
39128
|
+
padding: 8px 10px;
|
|
39129
|
+
}
|
|
39130
|
+
</style>
|
|
39131
|
+
<script type="module">
|
|
39132
|
+
const root = document.getElementById("root");
|
|
39133
|
+
|
|
39134
|
+
function formatMoney(value) {
|
|
39135
|
+
if (typeof value !== "number") return "n/a";
|
|
39136
|
+
if (Math.abs(value) >= 1000000) return "$" + (value / 1000000).toFixed(1) + "B";
|
|
39137
|
+
if (Math.abs(value) >= 1000) return "$" + (value / 1000).toFixed(1) + "M";
|
|
39138
|
+
return "$" + value.toLocaleString() + "k";
|
|
39139
|
+
}
|
|
39140
|
+
|
|
39141
|
+
function escapeHtml(value) {
|
|
39142
|
+
return String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
39143
|
+
"&": "&",
|
|
39144
|
+
"<": "<",
|
|
39145
|
+
">": ">",
|
|
39146
|
+
'"': """,
|
|
39147
|
+
"'": "'",
|
|
39148
|
+
}[char]));
|
|
39149
|
+
}
|
|
39150
|
+
|
|
39151
|
+
function render(data) {
|
|
39152
|
+
const institution = data?.institution ?? {};
|
|
39153
|
+
const assessment = data?.assessment ?? {};
|
|
39154
|
+
const metrics = data?.metrics ?? {};
|
|
39155
|
+
const signals = data?.risk_signals ?? [];
|
|
39156
|
+
const warnings = data?.warnings ?? [];
|
|
39157
|
+
const sources = data?.sources ?? [];
|
|
39158
|
+
const title = institution.name || "FDIC bank dashboard";
|
|
39159
|
+
|
|
39160
|
+
const signalItems = signals.length
|
|
39161
|
+
? signals.map((signal) => "<li>" + escapeHtml(signal) + "</li>").join("")
|
|
39162
|
+
: "<li>No risk signals returned for this dashboard.</li>";
|
|
39163
|
+
const warningSection = warnings.length
|
|
39164
|
+
? '<section class="section"><h2>Warnings</h2><ul class="list">' +
|
|
39165
|
+
warnings.map((warning) => "<li>" + escapeHtml(warning) + "</li>").join("") +
|
|
39166
|
+
"</ul></section>"
|
|
39167
|
+
: "";
|
|
39168
|
+
const sourceItems = sources
|
|
39169
|
+
.map((source) => '<li><a href="' + escapeHtml(source.url) + '" target="_blank" rel="noreferrer">' + escapeHtml(source.title) + "</a></li>")
|
|
39170
|
+
.join("");
|
|
39171
|
+
|
|
39172
|
+
root.innerHTML = [
|
|
39173
|
+
'<article class="panel">',
|
|
39174
|
+
'<header class="header">',
|
|
39175
|
+
'<p class="eyebrow">FDIC BankFind</p>',
|
|
39176
|
+
"<h1>" + escapeHtml(title) + "</h1>",
|
|
39177
|
+
'<p class="subtle">' + escapeHtml(institution.city) + ", " + escapeHtml(institution.state) + " · CERT " + escapeHtml(institution.cert) + " · " + escapeHtml(institution.active ? "Active" : "Inactive or unknown") + "</p>",
|
|
39178
|
+
'<p class="subtle">Report date: ' + escapeHtml(institution.report_date ?? "latest available") + " · Public analytical proxy, not an official CAMELS rating.</p>",
|
|
39179
|
+
"</header>",
|
|
39180
|
+
'<dl class="grid">',
|
|
39181
|
+
'<div class="metric"><dt>Assets</dt><dd>' + formatMoney(institution.asset_thousands) + "</dd></div>",
|
|
39182
|
+
'<div class="metric"><dt>Deposits</dt><dd>' + formatMoney(institution.deposit_thousands) + "</dd></div>",
|
|
39183
|
+
'<div class="metric"><dt>Offices</dt><dd>' + escapeHtml(institution.offices ?? "n/a") + "</dd></div>",
|
|
39184
|
+
'<div class="metric"><dt>Proxy band</dt><dd>' + escapeHtml(assessment.proxy_band ?? "n/a") + "</dd></div>",
|
|
39185
|
+
'<div class="metric"><dt>ROA</dt><dd>' + escapeHtml(metrics.roa ?? "n/a") + "</dd></div>",
|
|
39186
|
+
'<div class="metric"><dt>Tier 1 leverage</dt><dd>' + escapeHtml(metrics.tier1_leverage ?? "n/a") + "</dd></div>",
|
|
39187
|
+
"</dl>",
|
|
39188
|
+
'<section class="section"><h2>Risk Signals</h2><ul class="list">' + signalItems + "</ul></section>",
|
|
39189
|
+
warningSection,
|
|
39190
|
+
'<section class="section"><h2>Sources</h2><ul class="list">' + sourceItems + "</ul></section>",
|
|
39191
|
+
'<div class="actions">',
|
|
39192
|
+
'<button type="button" data-message="Compare CERT ' + escapeHtml(institution.cert) + ' with peers.">Compare peers</button>',
|
|
39193
|
+
'<button type="button" data-message="Show the branch footprint for CERT ' + escapeHtml(institution.cert) + '.">Branch footprint</button>',
|
|
39194
|
+
'<button type="button" data-message="Analyze the funding profile for CERT ' + escapeHtml(institution.cert) + '.">Funding profile</button>',
|
|
39195
|
+
"</div>",
|
|
39196
|
+
"</article>",
|
|
39197
|
+
].join("");
|
|
39198
|
+
}
|
|
39199
|
+
|
|
39200
|
+
window.addEventListener("message", (event) => {
|
|
39201
|
+
const message = event.data;
|
|
39202
|
+
if (message?.method === "ui/notifications/tool-result") {
|
|
39203
|
+
render(message.params?.structuredContent);
|
|
39204
|
+
}
|
|
39205
|
+
});
|
|
39206
|
+
|
|
39207
|
+
root.addEventListener("click", (event) => {
|
|
39208
|
+
const button = event.target.closest("button[data-message]");
|
|
39209
|
+
if (!button) return;
|
|
39210
|
+
const text = button.getAttribute("data-message");
|
|
39211
|
+
window.parent.postMessage({
|
|
39212
|
+
jsonrpc: "2.0",
|
|
39213
|
+
method: "ui/message",
|
|
39214
|
+
params: { role: "user", content: [{ type: "text", text }] },
|
|
39215
|
+
}, "*");
|
|
39216
|
+
});
|
|
39217
|
+
|
|
39218
|
+
const initial = window.openai?.toolOutput ?? window.openai?.toolResponse?.structuredContent;
|
|
39219
|
+
if (initial) {
|
|
39220
|
+
render(initial);
|
|
39221
|
+
}
|
|
39222
|
+
</script>
|
|
39223
|
+
`.trim();
|
|
39224
|
+
function registerChatGptAppResources(server) {
|
|
39225
|
+
server.registerResource(
|
|
39226
|
+
"fdic-bank-deep-dive-widget",
|
|
39227
|
+
BANK_DEEP_DIVE_WIDGET_URI,
|
|
39228
|
+
{
|
|
39229
|
+
title: "FDIC Bank Deep Dive Widget",
|
|
39230
|
+
description: "Interactive ChatGPT widget for a public FDIC bank deep-dive dashboard.",
|
|
39231
|
+
mimeType: MCP_APP_MIME_TYPE
|
|
39232
|
+
},
|
|
39233
|
+
async () => ({
|
|
39234
|
+
contents: [
|
|
39235
|
+
{
|
|
39236
|
+
uri: BANK_DEEP_DIVE_WIDGET_URI,
|
|
39237
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
39238
|
+
text: BANK_DEEP_DIVE_WIDGET_HTML,
|
|
39239
|
+
_meta: {
|
|
39240
|
+
ui: {
|
|
39241
|
+
prefersBorder: true,
|
|
39242
|
+
csp: {
|
|
39243
|
+
connectDomains: [],
|
|
39244
|
+
resourceDomains: []
|
|
39245
|
+
}
|
|
39246
|
+
},
|
|
39247
|
+
"openai/widgetDescription": "Renders an FDIC bank deep-dive dashboard from public BankFind data.",
|
|
39248
|
+
"openai/widgetPrefersBorder": true,
|
|
39249
|
+
"openai/widgetCSP": {
|
|
39250
|
+
connect_domains: [],
|
|
39251
|
+
resource_domains: []
|
|
39252
|
+
}
|
|
39253
|
+
}
|
|
39254
|
+
}
|
|
39255
|
+
]
|
|
39256
|
+
})
|
|
39257
|
+
);
|
|
39258
|
+
}
|
|
39259
|
+
|
|
39260
|
+
// src/tools/chatgptBankDeepDive.ts
|
|
39261
|
+
var BankDeepDiveInputSchema = import_zod22.z.object({
|
|
39262
|
+
cert: import_zod22.z.number().int().positive().describe("FDIC Certificate Number of the institution to render."),
|
|
39263
|
+
repdte: import_zod22.z.string().regex(/^\d{8}$/).optional().describe(
|
|
39264
|
+
"Quarter-end report date in YYYYMMDD format. Defaults to the most recent likely published quarter."
|
|
39265
|
+
)
|
|
39266
|
+
});
|
|
39267
|
+
function asNumber3(value) {
|
|
39268
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
39269
|
+
}
|
|
39270
|
+
function asString2(value) {
|
|
39271
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
39272
|
+
}
|
|
39273
|
+
function formatRatio(value) {
|
|
39274
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(2)}%` : void 0;
|
|
39275
|
+
}
|
|
39276
|
+
function collectDashboardRiskSignals(financials) {
|
|
39277
|
+
if (!financials) {
|
|
39278
|
+
return [];
|
|
39279
|
+
}
|
|
39280
|
+
const signals = [];
|
|
39281
|
+
const tier1Leverage = asNumber3(financials.IDT1CER);
|
|
39282
|
+
const roa = asNumber3(financials.ROA);
|
|
39283
|
+
const noncurrentLoans = asNumber3(financials.NCLNLSR);
|
|
39284
|
+
const brokeredDeposits = asNumber3(financials.BRO);
|
|
39285
|
+
if (tier1Leverage !== void 0 && tier1Leverage < 5) {
|
|
39286
|
+
signals.push(
|
|
39287
|
+
`Tier 1 leverage ratio is ${tier1Leverage.toFixed(2)}%, below the 5% well-capitalized threshold.`
|
|
39288
|
+
);
|
|
39289
|
+
}
|
|
39290
|
+
if (roa !== void 0 && roa < 0) {
|
|
39291
|
+
signals.push(`Return on assets is negative at ${roa.toFixed(2)}%.`);
|
|
39292
|
+
}
|
|
39293
|
+
if (noncurrentLoans !== void 0 && noncurrentLoans > 3) {
|
|
39294
|
+
signals.push(
|
|
39295
|
+
`Noncurrent loans ratio is elevated at ${noncurrentLoans.toFixed(2)}%.`
|
|
39296
|
+
);
|
|
39297
|
+
}
|
|
39298
|
+
if (brokeredDeposits !== void 0 && brokeredDeposits > 0) {
|
|
39299
|
+
signals.push(
|
|
39300
|
+
"Brokered deposit fields are present; review funding-profile analysis for reliance context."
|
|
39301
|
+
);
|
|
39302
|
+
}
|
|
39303
|
+
return signals;
|
|
39304
|
+
}
|
|
39305
|
+
function buildDashboardText(institution, repdte, riskSignals, warnings) {
|
|
39306
|
+
const lines = [
|
|
39307
|
+
`FDIC Bank Deep Dive: ${asString2(institution.NAME)} (CERT ${asString2(institution.CERT)})`,
|
|
39308
|
+
`${asString2(institution.CITY)}, ${asString2(institution.STALP)} | Report date: ${repdte}`,
|
|
39309
|
+
"This dashboard uses public FDIC BankFind data and is not an official CAMELS rating or supervisory conclusion.",
|
|
39310
|
+
"",
|
|
39311
|
+
`Assets: ${asString2(institution.ASSET) || "n/a"} ($thousands)`,
|
|
39312
|
+
`Deposits: ${asString2(institution.DEP) || "n/a"} ($thousands)`,
|
|
39313
|
+
`Offices: ${asString2(institution.OFFICES) || "n/a"}`
|
|
39314
|
+
];
|
|
39315
|
+
if (riskSignals.length > 0) {
|
|
39316
|
+
lines.push("", "Risk signals:", ...riskSignals.map((signal) => `- ${signal}`));
|
|
39317
|
+
}
|
|
39318
|
+
if (warnings.length > 0) {
|
|
39319
|
+
lines.push("", "Warnings:", ...warnings.map((warning) => `- ${warning}`));
|
|
39320
|
+
}
|
|
39321
|
+
return lines.join("\n");
|
|
39322
|
+
}
|
|
39323
|
+
function registerChatGptBankDeepDiveTool(server) {
|
|
39324
|
+
server.registerTool(
|
|
39325
|
+
"fdic_show_bank_deep_dive",
|
|
39326
|
+
{
|
|
39327
|
+
title: "Show Bank Deep Dive Dashboard",
|
|
39328
|
+
description: "Use this when the user wants a scannable ChatGPT dashboard for one FDIC-insured institution, including identity, public financial metrics, risk signals, and source links.",
|
|
39329
|
+
inputSchema: BankDeepDiveInputSchema,
|
|
39330
|
+
outputSchema: FdicBankDeepDiveOutputSchema,
|
|
39331
|
+
annotations: {
|
|
39332
|
+
readOnlyHint: true,
|
|
39333
|
+
destructiveHint: false,
|
|
39334
|
+
idempotentHint: true,
|
|
39335
|
+
openWorldHint: true
|
|
39336
|
+
},
|
|
39337
|
+
_meta: {
|
|
39338
|
+
ui: { resourceUri: BANK_DEEP_DIVE_WIDGET_URI },
|
|
39339
|
+
"openai/outputTemplate": BANK_DEEP_DIVE_WIDGET_URI,
|
|
39340
|
+
"openai/toolInvocation/invoking": "Building bank dashboard...",
|
|
39341
|
+
"openai/toolInvocation/invoked": "Bank dashboard ready"
|
|
39342
|
+
}
|
|
39343
|
+
},
|
|
39344
|
+
async (rawParams) => {
|
|
39345
|
+
const repdte = rawParams.repdte ?? getDefaultReportDate();
|
|
39346
|
+
const dateError = validateQuarterEndDate(repdte, "repdte");
|
|
39347
|
+
if (dateError) {
|
|
39348
|
+
return formatToolError(new Error(dateError));
|
|
39349
|
+
}
|
|
39350
|
+
try {
|
|
39351
|
+
const [institutionResponse, financialsResponse] = await Promise.all([
|
|
39352
|
+
queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
39353
|
+
filters: `CERT:${rawParams.cert}`,
|
|
39354
|
+
fields: "CERT,NAME,CITY,STALP,STNAME,ACTIVE,ASSET,DEP,OFFICES,BKCLASS,REGAGNT,ESTYMD",
|
|
39355
|
+
limit: 1
|
|
39356
|
+
}),
|
|
39357
|
+
queryEndpoint(ENDPOINTS.FINANCIALS, {
|
|
39358
|
+
filters: `CERT:${rawParams.cert} AND REPDTE:${repdte}`,
|
|
39359
|
+
fields: "CERT,REPDTE,ASSET,DEP,ROA,ROE,IDT1CER,NCLNLSR,LNLSDEPR,NIMY,EEFFR",
|
|
39360
|
+
limit: 1
|
|
39361
|
+
})
|
|
39362
|
+
]);
|
|
39363
|
+
const institution = extractRecords(institutionResponse)[0];
|
|
39364
|
+
if (!institution) {
|
|
39365
|
+
return formatToolError(
|
|
39366
|
+
new Error(`No institution found with CERT number ${rawParams.cert}.`)
|
|
39367
|
+
);
|
|
39368
|
+
}
|
|
39369
|
+
const financials = extractRecords(financialsResponse)[0];
|
|
39370
|
+
const warnings = financials ? [] : [
|
|
39371
|
+
`No financial record found for CERT ${rawParams.cert} at ${repdte}. Try an earlier quarter-end date.`
|
|
39372
|
+
];
|
|
39373
|
+
const riskSignals = collectDashboardRiskSignals(financials);
|
|
39374
|
+
const structuredContent = {
|
|
39375
|
+
institution: {
|
|
39376
|
+
cert: rawParams.cert,
|
|
39377
|
+
name: asString2(institution.NAME),
|
|
39378
|
+
city: asString2(institution.CITY),
|
|
39379
|
+
state: asString2(institution.STALP),
|
|
39380
|
+
active: institution.ACTIVE === 1 || institution.ACTIVE === "1",
|
|
39381
|
+
asset_thousands: asNumber3(financials?.ASSET) ?? asNumber3(institution.ASSET),
|
|
39382
|
+
deposit_thousands: asNumber3(financials?.DEP) ?? asNumber3(institution.DEP),
|
|
39383
|
+
offices: asNumber3(institution.OFFICES),
|
|
39384
|
+
charter_class: asString2(institution.BKCLASS),
|
|
39385
|
+
regulator: asString2(institution.REGAGNT),
|
|
39386
|
+
established: asString2(institution.ESTYMD),
|
|
39387
|
+
report_date: repdte
|
|
39388
|
+
},
|
|
39389
|
+
assessment: {
|
|
39390
|
+
official_rating: false,
|
|
39391
|
+
proxy_band: riskSignals.length > 0 ? "review" : "no major public flags",
|
|
39392
|
+
caveat: "Public off-site analytical dashboard; not an official CAMELS rating or confidential supervisory conclusion."
|
|
39393
|
+
},
|
|
39394
|
+
metrics: {
|
|
39395
|
+
roa: formatRatio(financials?.ROA),
|
|
39396
|
+
roe: formatRatio(financials?.ROE),
|
|
39397
|
+
tier1_leverage: formatRatio(financials?.IDT1CER),
|
|
39398
|
+
noncurrent_loans: formatRatio(financials?.NCLNLSR),
|
|
39399
|
+
loan_to_deposit: formatRatio(financials?.LNLSDEPR),
|
|
39400
|
+
net_interest_margin: formatRatio(financials?.NIMY),
|
|
39401
|
+
efficiency_ratio: formatRatio(financials?.EEFFR)
|
|
39402
|
+
},
|
|
39403
|
+
risk_signals: riskSignals,
|
|
39404
|
+
warnings,
|
|
39405
|
+
sources: [
|
|
39406
|
+
{
|
|
39407
|
+
title: "FDIC BankFind institution profile",
|
|
39408
|
+
url: getInstitutionUrl(rawParams.cert)
|
|
39409
|
+
}
|
|
39410
|
+
]
|
|
39411
|
+
};
|
|
39412
|
+
return {
|
|
39413
|
+
content: [
|
|
39414
|
+
{
|
|
39415
|
+
type: "text",
|
|
39416
|
+
text: truncateIfNeeded(
|
|
39417
|
+
buildDashboardText(institution, repdte, riskSignals, warnings),
|
|
39418
|
+
CHARACTER_LIMIT
|
|
39419
|
+
)
|
|
39420
|
+
}
|
|
39421
|
+
],
|
|
39422
|
+
structuredContent,
|
|
39423
|
+
_meta: {
|
|
39424
|
+
widget: {
|
|
39425
|
+
resourceUri: BANK_DEEP_DIVE_WIDGET_URI
|
|
39426
|
+
},
|
|
39427
|
+
raw: {
|
|
39428
|
+
institution,
|
|
39429
|
+
financials: financials ?? null
|
|
39430
|
+
}
|
|
39431
|
+
}
|
|
39432
|
+
};
|
|
39433
|
+
} catch (err) {
|
|
39434
|
+
return formatToolError(err);
|
|
39435
|
+
}
|
|
39436
|
+
}
|
|
39437
|
+
);
|
|
39438
|
+
}
|
|
39439
|
+
|
|
38642
39440
|
// src/resources/schemaResources.ts
|
|
38643
39441
|
var RESOURCE_SCHEME = "fdic";
|
|
38644
39442
|
var INDEX_URI = `${RESOURCE_SCHEME}://schemas/index`;
|
|
@@ -38709,33 +39507,218 @@ function registerSchemaResources(server) {
|
|
|
38709
39507
|
}
|
|
38710
39508
|
}
|
|
38711
39509
|
|
|
39510
|
+
// src/prompts/workflows.ts
|
|
39511
|
+
var import_zod23 = require("zod");
|
|
39512
|
+
var BankDeepDiveArgs = {
|
|
39513
|
+
bank: import_zod23.z.string().min(1).describe("Bank name or FDIC Certificate Number (CERT)."),
|
|
39514
|
+
repdte: import_zod23.z.string().regex(/^\d{8}$/).optional().describe(
|
|
39515
|
+
"Optional quarter-end report date in YYYYMMDD format (0331, 0630, 0930, or 1231)."
|
|
39516
|
+
)
|
|
39517
|
+
};
|
|
39518
|
+
var FailureForensicsArgs = {
|
|
39519
|
+
bank: import_zod23.z.string().min(1).describe("Failed bank name or FDIC Certificate Number (CERT)."),
|
|
39520
|
+
lookback_quarters: import_zod23.z.string().regex(/^\d+$/).optional().describe(
|
|
39521
|
+
"Number of pre-failure quarters to reconstruct (default 12 if omitted)."
|
|
39522
|
+
)
|
|
39523
|
+
};
|
|
39524
|
+
var PortfolioSurveillanceArgs = {
|
|
39525
|
+
scope: import_zod23.z.string().min(1).describe(
|
|
39526
|
+
"Universe to screen \u2014 e.g., 'state:NC', 'asset_min:1000000,asset_max:10000000', or a comma-separated CERT list ('certs:3511,29846,...')."
|
|
39527
|
+
),
|
|
39528
|
+
repdte: import_zod23.z.string().regex(/^\d{8}$/).optional().describe("Optional quarter-end report date in YYYYMMDD format.")
|
|
39529
|
+
};
|
|
39530
|
+
var ExaminerOverlayArgs = {
|
|
39531
|
+
bank: import_zod23.z.string().min(1).describe("Bank name or FDIC Certificate Number (CERT)."),
|
|
39532
|
+
qualitative_notes: import_zod23.z.string().optional().describe(
|
|
39533
|
+
"Optional qualitative analyst inputs (management quality, governance, exam findings) to overlay onto the public proxy assessment."
|
|
39534
|
+
)
|
|
39535
|
+
};
|
|
39536
|
+
function userText(text) {
|
|
39537
|
+
return {
|
|
39538
|
+
role: "user",
|
|
39539
|
+
content: { type: "text", text }
|
|
39540
|
+
};
|
|
39541
|
+
}
|
|
39542
|
+
function registerWorkflowPrompts(server) {
|
|
39543
|
+
server.registerPrompt(
|
|
39544
|
+
"bank_deep_dive",
|
|
39545
|
+
{
|
|
39546
|
+
title: "Comprehensive Bank Deep Dive",
|
|
39547
|
+
description: "Produce a comprehensive single-institution analysis report (health, financials, peer benchmarking, credit concentration, funding profile, securities, franchise footprint, regional context).",
|
|
39548
|
+
argsSchema: BankDeepDiveArgs
|
|
39549
|
+
},
|
|
39550
|
+
({ bank, repdte }) => ({
|
|
39551
|
+
messages: [
|
|
39552
|
+
userText(
|
|
39553
|
+
[
|
|
39554
|
+
`Run a comprehensive FDIC bank deep dive for "${bank}"${repdte ? ` as of ${repdte}` : ""}.`,
|
|
39555
|
+
"",
|
|
39556
|
+
"Steps:",
|
|
39557
|
+
"1. If a CERT was not given, call fdic_search_institutions to resolve the bank to a CERT.",
|
|
39558
|
+
"2. Call fdic_analyze_bank_health and fdic_ubpr_analysis for the resolved CERT.",
|
|
39559
|
+
"3. Call fdic_peer_group_analysis to benchmark the institution against peers.",
|
|
39560
|
+
"4. Call fdic_analyze_credit_concentration, fdic_analyze_funding_profile, and fdic_analyze_securities_portfolio.",
|
|
39561
|
+
"5. Call fdic_franchise_footprint for branch/deposit geography and fdic_regional_context for the home market.",
|
|
39562
|
+
"6. Synthesize into a narrative report with: identity & footprint, health summary, financial performance, peer position, credit concentration, funding profile, securities portfolio, regional context, and a public-data caveat.",
|
|
39563
|
+
"",
|
|
39564
|
+
"All output must be grounded in tool results. Treat any CAMELS-style scoring as a public off-site analytical proxy \u2014 not an official supervisory rating. Note unit conventions ($thousands) where relevant."
|
|
39565
|
+
].join("\n")
|
|
39566
|
+
)
|
|
39567
|
+
]
|
|
39568
|
+
})
|
|
39569
|
+
);
|
|
39570
|
+
server.registerPrompt(
|
|
39571
|
+
"failure_forensics",
|
|
39572
|
+
{
|
|
39573
|
+
title: "Failed Bank Forensics",
|
|
39574
|
+
description: "Reconstruct the pre-failure financial timeline of a failed FDIC institution and identify the earliest visible warning signals.",
|
|
39575
|
+
argsSchema: FailureForensicsArgs
|
|
39576
|
+
},
|
|
39577
|
+
({ bank, lookback_quarters }) => ({
|
|
39578
|
+
messages: [
|
|
39579
|
+
userText(
|
|
39580
|
+
[
|
|
39581
|
+
`Run a failure-forensics post-mortem for "${bank}" using the FDIC tools.`,
|
|
39582
|
+
"",
|
|
39583
|
+
"Steps:",
|
|
39584
|
+
"1. Resolve the bank to a CERT via fdic_search_institutions or fdic_search.",
|
|
39585
|
+
"2. Call fdic_get_institution_failure to confirm failure date, resolution type, and cost.",
|
|
39586
|
+
`3. Call fdic_search_financials with CERT and a sort_by:REPDTE DESC ordering to pull the prior ${lookback_quarters ?? "12"} quarters before the failure date.`,
|
|
39587
|
+
"4. Call fdic_analyze_credit_concentration, fdic_analyze_funding_profile, and fdic_analyze_securities_portfolio at the latest available pre-failure quarter.",
|
|
39588
|
+
"5. Call fdic_search_history for structural-change events (mergers, charter conversions, assistance) leading up to the failure.",
|
|
39589
|
+
"6. Synthesize a forensic timeline with: failure facts, the deterioration arc (capital, asset quality, earnings, liquidity), the earliest warning signals visible in public data, and likely drivers.",
|
|
39590
|
+
"",
|
|
39591
|
+
"Highlight inflection points (e.g. quarter ROA turned negative, when noncurrent loans exceeded reserves). Reference each finding to the report date that supports it."
|
|
39592
|
+
].join("\n")
|
|
39593
|
+
)
|
|
39594
|
+
]
|
|
39595
|
+
})
|
|
39596
|
+
);
|
|
39597
|
+
server.registerPrompt(
|
|
39598
|
+
"portfolio_surveillance",
|
|
39599
|
+
{
|
|
39600
|
+
title: "Portfolio Surveillance Watchlist",
|
|
39601
|
+
description: "Screen a universe of FDIC institutions and produce a decision-ready watchlist tiered Escalate / Monitor / No Immediate Concern.",
|
|
39602
|
+
argsSchema: PortfolioSurveillanceArgs
|
|
39603
|
+
},
|
|
39604
|
+
({ scope, repdte }) => ({
|
|
39605
|
+
messages: [
|
|
39606
|
+
userText(
|
|
39607
|
+
[
|
|
39608
|
+
`Run portfolio surveillance over scope: "${scope}"${repdte ? ` as of ${repdte}` : ""}.`,
|
|
39609
|
+
"",
|
|
39610
|
+
"Steps:",
|
|
39611
|
+
"1. Build the institution roster:",
|
|
39612
|
+
" - state:<XX> \u2192 fdic_search_institutions filters STALP:<XX> AND ACTIVE:1",
|
|
39613
|
+
" - asset_min:<n>,asset_max:<n> \u2192 ASSET range",
|
|
39614
|
+
" - certs:<csv> \u2192 use the comma-separated list directly",
|
|
39615
|
+
"2. Call fdic_detect_risk_signals across the roster.",
|
|
39616
|
+
"3. Call fdic_compare_peer_health to rank by composite proxy band.",
|
|
39617
|
+
"4. For the highest-risk subset, call fdic_analyze_bank_health for full proxy assessments.",
|
|
39618
|
+
"5. Tier institutions:",
|
|
39619
|
+
" - Escalate: critical risk signals or proxy band 'unsatisfactory'.",
|
|
39620
|
+
" - Monitor: warning signals or 'fair' band, especially deteriorating trends.",
|
|
39621
|
+
" - No Immediate Concern: no critical signals, satisfactory or strong band.",
|
|
39622
|
+
"6. Produce a watchlist table per tier (CERT, name, key signals, rationale).",
|
|
39623
|
+
"",
|
|
39624
|
+
"Treat all scoring as a public off-site analytical proxy. Surface data caveats explicitly."
|
|
39625
|
+
].join("\n")
|
|
39626
|
+
)
|
|
39627
|
+
]
|
|
39628
|
+
})
|
|
39629
|
+
);
|
|
39630
|
+
server.registerPrompt(
|
|
39631
|
+
"examiner_overlay",
|
|
39632
|
+
{
|
|
39633
|
+
title: "Examiner Overlay Assessment",
|
|
39634
|
+
description: "Layer qualitative analyst/examiner inputs on top of the public CAMELS proxy and produce a blended assessment with explicit provenance.",
|
|
39635
|
+
argsSchema: ExaminerOverlayArgs
|
|
39636
|
+
},
|
|
39637
|
+
({ bank, qualitative_notes }) => ({
|
|
39638
|
+
messages: [
|
|
39639
|
+
userText(
|
|
39640
|
+
[
|
|
39641
|
+
`Produce an examiner-overlay assessment for "${bank}".`,
|
|
39642
|
+
"",
|
|
39643
|
+
"Steps:",
|
|
39644
|
+
"1. Resolve to a CERT via fdic_search_institutions if needed.",
|
|
39645
|
+
"2. Call fdic_analyze_bank_health to get the public_camels_proxy_v1 baseline (capital, asset quality, earnings, liquidity, sensitivity).",
|
|
39646
|
+
"3. Call fdic_ubpr_analysis for performance ratios and fdic_analyze_funding_profile / fdic_analyze_credit_concentration for sub-component depth.",
|
|
39647
|
+
"4. Combine the public baseline with qualitative analyst inputs:",
|
|
39648
|
+
qualitative_notes ? ` Analyst notes: ${qualitative_notes}` : " (No qualitative inputs provided \u2014 produce the public baseline and clearly mark which factors would benefit from qualitative overlay.)",
|
|
39649
|
+
"5. Produce a blended assessment with two columns labeled exactly: 'Public-data finding' and 'Examiner overlay'. Never present overlay inputs as if they came from public data.",
|
|
39650
|
+
"6. Final composite must reconcile both columns and call out any divergence.",
|
|
39651
|
+
"",
|
|
39652
|
+
"Disclaimer: this overlay is not an official CAMELS rating or confidential supervisory conclusion."
|
|
39653
|
+
].join("\n")
|
|
39654
|
+
)
|
|
39655
|
+
]
|
|
39656
|
+
})
|
|
39657
|
+
);
|
|
39658
|
+
}
|
|
39659
|
+
|
|
38712
39660
|
// src/index.ts
|
|
38713
|
-
function
|
|
39661
|
+
function resolveProfile(raw) {
|
|
39662
|
+
const tokens = (raw ?? "all").split(",").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
|
|
39663
|
+
const has = (token) => tokens.includes(token);
|
|
39664
|
+
const all = has("all") || tokens.length === 0;
|
|
39665
|
+
const includeChatgpt = all || has("chatgpt");
|
|
39666
|
+
return {
|
|
39667
|
+
core: all || has("core"),
|
|
39668
|
+
analysis: all || has("analysis"),
|
|
39669
|
+
chatgptCanonical: all || includeChatgpt || has("chatgpt-canonical"),
|
|
39670
|
+
chatgptAliases: all || includeChatgpt || has("chatgpt-aliases"),
|
|
39671
|
+
chatgptDeepDive: all || includeChatgpt,
|
|
39672
|
+
prompts: all || has("prompts"),
|
|
39673
|
+
resources: all || has("resources")
|
|
39674
|
+
};
|
|
39675
|
+
}
|
|
39676
|
+
function createServer(options = {}) {
|
|
38714
39677
|
const server = new import_mcp.McpServer({
|
|
38715
39678
|
name: "fdic-mcp-server",
|
|
38716
39679
|
version: VERSION
|
|
38717
39680
|
});
|
|
38718
|
-
|
|
38719
|
-
|
|
38720
|
-
|
|
38721
|
-
|
|
38722
|
-
|
|
38723
|
-
|
|
38724
|
-
|
|
38725
|
-
|
|
38726
|
-
|
|
38727
|
-
|
|
38728
|
-
|
|
38729
|
-
|
|
38730
|
-
|
|
38731
|
-
|
|
38732
|
-
|
|
38733
|
-
|
|
38734
|
-
|
|
38735
|
-
|
|
38736
|
-
|
|
38737
|
-
|
|
38738
|
-
|
|
39681
|
+
const profile = resolveProfile(options.profile ?? process.env.FDIC_MCP_PROFILE);
|
|
39682
|
+
if (profile.core) {
|
|
39683
|
+
registerInstitutionTools(server);
|
|
39684
|
+
registerFailureTools(server);
|
|
39685
|
+
registerLocationTools(server);
|
|
39686
|
+
registerHistoryTools(server);
|
|
39687
|
+
registerFinancialTools(server);
|
|
39688
|
+
registerSodTools(server);
|
|
39689
|
+
registerDemographicsTools(server);
|
|
39690
|
+
}
|
|
39691
|
+
if (profile.analysis) {
|
|
39692
|
+
registerAnalysisTools(server);
|
|
39693
|
+
registerPeerGroupTools(server);
|
|
39694
|
+
registerBankHealthTools(server);
|
|
39695
|
+
registerPeerHealthTools(server);
|
|
39696
|
+
registerRiskSignalTools(server);
|
|
39697
|
+
registerCreditConcentrationTools(server);
|
|
39698
|
+
registerFundingProfileTools(server);
|
|
39699
|
+
registerSecuritiesPortfolioTools(server);
|
|
39700
|
+
registerUbprAnalysisTools(server);
|
|
39701
|
+
registerMarketShareAnalysisTools(server);
|
|
39702
|
+
registerFranchiseFootprintTools(server);
|
|
39703
|
+
registerHoldingCompanyProfileTools(server);
|
|
39704
|
+
registerRegionalContextTools(server);
|
|
39705
|
+
}
|
|
39706
|
+
if (profile.chatgptCanonical || profile.chatgptAliases) {
|
|
39707
|
+
registerChatGptRetrievalTools(server, {
|
|
39708
|
+
includeCanonicalNames: profile.chatgptCanonical,
|
|
39709
|
+
includeNamespacedAliases: profile.chatgptAliases
|
|
39710
|
+
});
|
|
39711
|
+
}
|
|
39712
|
+
if (profile.chatgptDeepDive) {
|
|
39713
|
+
registerChatGptBankDeepDiveTool(server);
|
|
39714
|
+
registerChatGptAppResources(server);
|
|
39715
|
+
}
|
|
39716
|
+
if (profile.resources) {
|
|
39717
|
+
registerSchemaResources(server);
|
|
39718
|
+
}
|
|
39719
|
+
if (profile.prompts) {
|
|
39720
|
+
registerWorkflowPrompts(server);
|
|
39721
|
+
}
|
|
38739
39722
|
return server;
|
|
38740
39723
|
}
|
|
38741
39724
|
async function runStdio() {
|
|
@@ -38807,23 +39790,61 @@ function sendInvalidSessionResponse(res) {
|
|
|
38807
39790
|
}
|
|
38808
39791
|
function createApp(options = {}) {
|
|
38809
39792
|
const app = (0, import_express2.default)();
|
|
38810
|
-
const serverFactory = options.serverFactory ?? createServer;
|
|
39793
|
+
const serverFactory = options.serverFactory ?? (() => createServer());
|
|
38811
39794
|
const port = options.port ?? 3e3;
|
|
38812
39795
|
const allowedOrigins = options.allowedOrigins ?? parseAllowedOrigins(void 0, port);
|
|
38813
39796
|
const sessions = /* @__PURE__ */ new Map();
|
|
38814
39797
|
const chatSessions = /* @__PURE__ */ new Map();
|
|
38815
39798
|
const sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
|
|
38816
39799
|
const sessionSweepIntervalMs = options.sessionSweepIntervalMs ?? DEFAULT_SESSION_SWEEP_INTERVAL_MS;
|
|
39800
|
+
const stateless = options.stateless ?? process.env.FDIC_MCP_STATELESS_HTTP === "true";
|
|
38817
39801
|
app.use(import_express2.default.json());
|
|
38818
|
-
|
|
38819
|
-
|
|
38820
|
-
|
|
38821
|
-
|
|
38822
|
-
|
|
39802
|
+
if (!stateless) {
|
|
39803
|
+
const sessionSweepTimer = setInterval(() => {
|
|
39804
|
+
void sweepIdleSessions(sessions, sessionIdleTimeoutMs, Date.now());
|
|
39805
|
+
sweepIdleChatSessions(chatSessions, sessionIdleTimeoutMs, Date.now());
|
|
39806
|
+
}, sessionSweepIntervalMs);
|
|
39807
|
+
sessionSweepTimer.unref?.();
|
|
39808
|
+
}
|
|
38823
39809
|
app.get("/health", (_req, res) => {
|
|
38824
39810
|
res.json({ status: "ok", server: "fdic-mcp-server", version: VERSION });
|
|
38825
39811
|
});
|
|
38826
39812
|
app.all("/mcp", async (req, res) => {
|
|
39813
|
+
if (stateless) {
|
|
39814
|
+
let server;
|
|
39815
|
+
let transport;
|
|
39816
|
+
try {
|
|
39817
|
+
server = serverFactory();
|
|
39818
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
39819
|
+
sessionIdGenerator: void 0,
|
|
39820
|
+
enableJsonResponse: true,
|
|
39821
|
+
enableDnsRebindingProtection: true,
|
|
39822
|
+
allowedOrigins
|
|
39823
|
+
});
|
|
39824
|
+
res.on("close", () => {
|
|
39825
|
+
void transport?.close().catch(() => {
|
|
39826
|
+
});
|
|
39827
|
+
void server?.close().catch(() => {
|
|
39828
|
+
});
|
|
39829
|
+
});
|
|
39830
|
+
await server.connect(transport);
|
|
39831
|
+
await transport.handleRequest(req, res, req.body);
|
|
39832
|
+
} catch (error) {
|
|
39833
|
+
console.error("MCP request error:", error);
|
|
39834
|
+
if (!res.headersSent) {
|
|
39835
|
+
res.status(500).json({
|
|
39836
|
+
jsonrpc: "2.0",
|
|
39837
|
+
error: { code: -32603, message: "Internal server error" },
|
|
39838
|
+
id: null
|
|
39839
|
+
});
|
|
39840
|
+
}
|
|
39841
|
+
await transport?.close().catch(() => {
|
|
39842
|
+
});
|
|
39843
|
+
await server?.close().catch(() => {
|
|
39844
|
+
});
|
|
39845
|
+
}
|
|
39846
|
+
return;
|
|
39847
|
+
}
|
|
38827
39848
|
const sessionIdHeader = req.headers["mcp-session-id"];
|
|
38828
39849
|
const sessionId = typeof sessionIdHeader === "string" ? sessionIdHeader : void 0;
|
|
38829
39850
|
try {
|
|
@@ -38932,5 +39953,6 @@ async function main() {
|
|
|
38932
39953
|
main,
|
|
38933
39954
|
parseAllowedOrigins,
|
|
38934
39955
|
parseHttpHost,
|
|
38935
|
-
parseHttpPort
|
|
39956
|
+
parseHttpPort,
|
|
39957
|
+
resolveProfile
|
|
38936
39958
|
});
|