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/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
32
32
|
var import_express2 = __toESM(require("express"));
|
|
33
33
|
|
|
34
34
|
// src/constants.ts
|
|
35
|
-
var VERSION = true ? "1.
|
|
35
|
+
var VERSION = true ? "1.25.0" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
36
36
|
var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
|
|
37
37
|
var CHARACTER_LIMIT = 5e4;
|
|
38
38
|
var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
@@ -31846,13 +31846,96 @@ function formatLookupResultText(label, record, preferredKeys) {
|
|
|
31846
31846
|
return `${label}
|
|
31847
31847
|
${summarizeRecord(record, preferredKeys, 8)}`;
|
|
31848
31848
|
}
|
|
31849
|
-
|
|
31849
|
+
var ERROR_CODE_FROM_MESSAGE = [
|
|
31850
|
+
{ pattern: /Bad request to FDIC API/, code: "FDIC_BAD_FILTER", retryable: false },
|
|
31851
|
+
{ pattern: /rate limit/i, code: "FDIC_RATE_LIMIT", retryable: true },
|
|
31852
|
+
{ pattern: /server error/i, code: "FDIC_UPSTREAM_ERROR", retryable: true },
|
|
31853
|
+
{
|
|
31854
|
+
pattern: /response-size limit|maxContentLength/,
|
|
31855
|
+
code: "FDIC_RESPONSE_TOO_LARGE",
|
|
31856
|
+
retryable: false
|
|
31857
|
+
},
|
|
31858
|
+
{ pattern: /canceled/i, code: "FDIC_CANCELED", retryable: true },
|
|
31859
|
+
{ pattern: /No (institution|failure|financial)/i, code: "FDIC_NOT_FOUND", retryable: false },
|
|
31860
|
+
{ pattern: /quarter-end date/i, code: "FDIC_BAD_DATE", retryable: false }
|
|
31861
|
+
];
|
|
31862
|
+
function inferErrorCode(message) {
|
|
31863
|
+
for (const entry of ERROR_CODE_FROM_MESSAGE) {
|
|
31864
|
+
if (entry.pattern.test(message)) {
|
|
31865
|
+
return { code: entry.code, retryable: entry.retryable };
|
|
31866
|
+
}
|
|
31867
|
+
}
|
|
31868
|
+
return { code: "FDIC_UNKNOWN", retryable: false };
|
|
31869
|
+
}
|
|
31870
|
+
function formatToolError(err, override) {
|
|
31850
31871
|
const message = err instanceof Error ? err.message : String(err);
|
|
31872
|
+
const inferred = inferErrorCode(message);
|
|
31873
|
+
const payload = {
|
|
31874
|
+
code: override?.code ?? inferred.code,
|
|
31875
|
+
message: override?.message ?? message,
|
|
31876
|
+
retryable: override?.retryable ?? inferred.retryable,
|
|
31877
|
+
hint: override?.hint
|
|
31878
|
+
};
|
|
31851
31879
|
return {
|
|
31852
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
31880
|
+
content: [{ type: "text", text: `Error: ${payload.message}` }],
|
|
31881
|
+
structuredContent: payload,
|
|
31853
31882
|
isError: true
|
|
31854
31883
|
};
|
|
31855
31884
|
}
|
|
31885
|
+
var DEFAULT_STRUCTURED_BYTE_LIMIT = 2e5;
|
|
31886
|
+
function capStructuredContent(output, recordKey, byteLimit = DEFAULT_STRUCTURED_BYTE_LIMIT) {
|
|
31887
|
+
const records = output[recordKey];
|
|
31888
|
+
if (!Array.isArray(records)) {
|
|
31889
|
+
return output;
|
|
31890
|
+
}
|
|
31891
|
+
const initialBytes = Buffer.byteLength(JSON.stringify(output), "utf8");
|
|
31892
|
+
if (initialBytes <= byteLimit) {
|
|
31893
|
+
return output;
|
|
31894
|
+
}
|
|
31895
|
+
let lo = 0;
|
|
31896
|
+
let hi = records.length;
|
|
31897
|
+
let best = 0;
|
|
31898
|
+
while (lo <= hi) {
|
|
31899
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
31900
|
+
const candidate = buildTruncatedPayload(output, recordKey, records, mid);
|
|
31901
|
+
const bytes = Buffer.byteLength(JSON.stringify(candidate), "utf8");
|
|
31902
|
+
if (bytes <= byteLimit) {
|
|
31903
|
+
best = mid;
|
|
31904
|
+
lo = mid + 1;
|
|
31905
|
+
} else {
|
|
31906
|
+
hi = mid - 1;
|
|
31907
|
+
}
|
|
31908
|
+
}
|
|
31909
|
+
if (best === 0) {
|
|
31910
|
+
throw new Error(
|
|
31911
|
+
"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."
|
|
31912
|
+
);
|
|
31913
|
+
}
|
|
31914
|
+
return buildTruncatedPayload(output, recordKey, records, best);
|
|
31915
|
+
}
|
|
31916
|
+
function buildTruncatedPayload(output, recordKey, records, slicedLength) {
|
|
31917
|
+
const sliced = records.slice(0, slicedLength);
|
|
31918
|
+
const result = {
|
|
31919
|
+
...output,
|
|
31920
|
+
[recordKey]: sliced,
|
|
31921
|
+
truncated: true
|
|
31922
|
+
};
|
|
31923
|
+
const offset = typeof output.offset === "number" ? output.offset : void 0;
|
|
31924
|
+
const upstreamCount = typeof output.count === "number" ? output.count : void 0;
|
|
31925
|
+
const upstreamNextOffset = typeof output.next_offset === "number" ? output.next_offset : void 0;
|
|
31926
|
+
if (offset !== void 0) {
|
|
31927
|
+
result.count = slicedLength;
|
|
31928
|
+
result.next_offset = offset + slicedLength;
|
|
31929
|
+
result.has_more = true;
|
|
31930
|
+
}
|
|
31931
|
+
if (upstreamCount !== void 0 || upstreamNextOffset !== void 0) {
|
|
31932
|
+
result.upstream = {
|
|
31933
|
+
...upstreamCount !== void 0 ? { count: upstreamCount } : {},
|
|
31934
|
+
...upstreamNextOffset !== void 0 ? { next_offset: upstreamNextOffset } : {}
|
|
31935
|
+
};
|
|
31936
|
+
}
|
|
31937
|
+
return result;
|
|
31938
|
+
}
|
|
31856
31939
|
|
|
31857
31940
|
// src/schemas/common.ts
|
|
31858
31941
|
var import_zod = require("zod");
|
|
@@ -31877,57 +31960,100 @@ var CertSchema = import_zod.z.object({
|
|
|
31877
31960
|
fields: import_zod.z.string().optional().describe("Comma-separated list of fields to return")
|
|
31878
31961
|
});
|
|
31879
31962
|
|
|
31963
|
+
// src/schemas/output.ts
|
|
31964
|
+
var import_zod2 = require("zod");
|
|
31965
|
+
var FdicRecord = import_zod2.z.record(import_zod2.z.unknown());
|
|
31966
|
+
var Pagination = {
|
|
31967
|
+
total: import_zod2.z.number().int(),
|
|
31968
|
+
offset: import_zod2.z.number().int(),
|
|
31969
|
+
count: import_zod2.z.number().int(),
|
|
31970
|
+
has_more: import_zod2.z.boolean(),
|
|
31971
|
+
next_offset: import_zod2.z.number().int().optional()
|
|
31972
|
+
};
|
|
31973
|
+
function paginatedSearchSchema(recordKey) {
|
|
31974
|
+
return import_zod2.z.object({
|
|
31975
|
+
...Pagination,
|
|
31976
|
+
[recordKey]: import_zod2.z.array(FdicRecord),
|
|
31977
|
+
truncated: import_zod2.z.boolean().optional()
|
|
31978
|
+
});
|
|
31979
|
+
}
|
|
31980
|
+
var FdicInstitutionsSearchOutputSchema = paginatedSearchSchema(
|
|
31981
|
+
"institutions"
|
|
31982
|
+
);
|
|
31983
|
+
var FdicFailuresSearchOutputSchema = paginatedSearchSchema("failures");
|
|
31984
|
+
var FdicLocationsSearchOutputSchema = paginatedSearchSchema("locations");
|
|
31985
|
+
var FdicHistorySearchOutputSchema = paginatedSearchSchema("events");
|
|
31986
|
+
var FdicFinancialsSearchOutputSchema = paginatedSearchSchema(
|
|
31987
|
+
"financials"
|
|
31988
|
+
);
|
|
31989
|
+
var FdicSummarySearchOutputSchema = paginatedSearchSchema("summary");
|
|
31990
|
+
var FdicSodSearchOutputSchema = paginatedSearchSchema("deposits");
|
|
31991
|
+
var FdicDemographicsSearchOutputSchema = paginatedSearchSchema(
|
|
31992
|
+
"demographics"
|
|
31993
|
+
);
|
|
31994
|
+
var FdicInstitutionLookupOutputSchema = import_zod2.z.object({}).passthrough();
|
|
31995
|
+
var FdicFailureLookupOutputSchema = import_zod2.z.object({}).passthrough();
|
|
31996
|
+
var ChatGptSearchResultSchema = import_zod2.z.object({
|
|
31997
|
+
results: import_zod2.z.array(
|
|
31998
|
+
import_zod2.z.object({
|
|
31999
|
+
id: import_zod2.z.string(),
|
|
32000
|
+
title: import_zod2.z.string(),
|
|
32001
|
+
url: import_zod2.z.string()
|
|
32002
|
+
})
|
|
32003
|
+
)
|
|
32004
|
+
});
|
|
32005
|
+
var ChatGptFetchResultSchema = import_zod2.z.object({
|
|
32006
|
+
id: import_zod2.z.string(),
|
|
32007
|
+
title: import_zod2.z.string(),
|
|
32008
|
+
text: import_zod2.z.string(),
|
|
32009
|
+
url: import_zod2.z.string(),
|
|
32010
|
+
metadata: import_zod2.z.record(import_zod2.z.unknown()).optional()
|
|
32011
|
+
});
|
|
32012
|
+
var Source = import_zod2.z.object({ title: import_zod2.z.string(), url: import_zod2.z.string() });
|
|
32013
|
+
var FdicBankDeepDiveOutputSchema = import_zod2.z.object({
|
|
32014
|
+
institution: import_zod2.z.object({
|
|
32015
|
+
cert: import_zod2.z.number().int(),
|
|
32016
|
+
name: import_zod2.z.string(),
|
|
32017
|
+
city: import_zod2.z.string(),
|
|
32018
|
+
state: import_zod2.z.string(),
|
|
32019
|
+
active: import_zod2.z.boolean(),
|
|
32020
|
+
asset_thousands: import_zod2.z.number().optional(),
|
|
32021
|
+
deposit_thousands: import_zod2.z.number().optional(),
|
|
32022
|
+
offices: import_zod2.z.number().optional(),
|
|
32023
|
+
charter_class: import_zod2.z.string(),
|
|
32024
|
+
regulator: import_zod2.z.string(),
|
|
32025
|
+
established: import_zod2.z.string(),
|
|
32026
|
+
report_date: import_zod2.z.string()
|
|
32027
|
+
}),
|
|
32028
|
+
assessment: import_zod2.z.object({
|
|
32029
|
+
official_rating: import_zod2.z.boolean(),
|
|
32030
|
+
proxy_band: import_zod2.z.string(),
|
|
32031
|
+
caveat: import_zod2.z.string()
|
|
32032
|
+
}),
|
|
32033
|
+
metrics: import_zod2.z.object({
|
|
32034
|
+
roa: import_zod2.z.string().optional(),
|
|
32035
|
+
roe: import_zod2.z.string().optional(),
|
|
32036
|
+
tier1_leverage: import_zod2.z.string().optional(),
|
|
32037
|
+
noncurrent_loans: import_zod2.z.string().optional(),
|
|
32038
|
+
loan_to_deposit: import_zod2.z.string().optional(),
|
|
32039
|
+
net_interest_margin: import_zod2.z.string().optional(),
|
|
32040
|
+
efficiency_ratio: import_zod2.z.string().optional()
|
|
32041
|
+
}),
|
|
32042
|
+
risk_signals: import_zod2.z.array(import_zod2.z.string()),
|
|
32043
|
+
warnings: import_zod2.z.array(import_zod2.z.string()),
|
|
32044
|
+
sources: import_zod2.z.array(Source)
|
|
32045
|
+
});
|
|
32046
|
+
var FdicAnalysisOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32047
|
+
|
|
31880
32048
|
// src/tools/institutions.ts
|
|
31881
32049
|
function registerInstitutionTools(server) {
|
|
31882
32050
|
server.registerTool(
|
|
31883
32051
|
"fdic_search_institutions",
|
|
31884
32052
|
{
|
|
31885
32053
|
title: "Search FDIC Institutions",
|
|
31886
|
-
description:
|
|
31887
|
-
|
|
31888
|
-
Returns institution profile data including name, location, charter class, asset size, deposit totals, profitability metrics, and regulatory status.
|
|
31889
|
-
|
|
31890
|
-
Common filter examples:
|
|
31891
|
-
- By state: STNAME:"California"
|
|
31892
|
-
- Active banks only: ACTIVE:1
|
|
31893
|
-
- Large banks: ASSET:[10000000 TO *] (assets in $thousands)
|
|
31894
|
-
- By bank class: BKCLASS:N (national bank), BKCLASS:SM (state member bank), BKCLASS:NM (state non-member)
|
|
31895
|
-
- By name: NAME:"Wells Fargo"
|
|
31896
|
-
- Commercial banks: CB:1
|
|
31897
|
-
- Savings institutions: MUTUAL:1
|
|
31898
|
-
- Recently established: ESTYMD:[2010-01-01 TO *]
|
|
31899
|
-
|
|
31900
|
-
Charter class codes (BKCLASS):
|
|
31901
|
-
N = National commercial bank (OCC-supervised)
|
|
31902
|
-
SM = State-chartered, Federal Reserve member
|
|
31903
|
-
NM = State-chartered, non-member (FDIC-supervised)
|
|
31904
|
-
SB = Federal savings bank (OCC-supervised)
|
|
31905
|
-
SA = State savings association
|
|
31906
|
-
OI = Insured branch of foreign bank
|
|
31907
|
-
|
|
31908
|
-
Key returned fields:
|
|
31909
|
-
- CERT: FDIC Certificate Number (unique ID)
|
|
31910
|
-
- NAME: Institution name
|
|
31911
|
-
- CITY, STALP (two-letter state code), STNAME (full state name): Location
|
|
31912
|
-
- ASSET: Total assets ($thousands)
|
|
31913
|
-
- DEP: Total deposits ($thousands)
|
|
31914
|
-
- BKCLASS: Charter class code (see above)
|
|
31915
|
-
- ACTIVE: 1 if currently active, 0 if inactive
|
|
31916
|
-
- ROA, ROE: Profitability ratios
|
|
31917
|
-
- OFFICES: Number of branch offices
|
|
31918
|
-
- ESTYMD: Establishment date (YYYY-MM-DD)
|
|
31919
|
-
- REGAGNT: Primary federal regulator (OCC, FRS, FDIC)
|
|
31920
|
-
|
|
31921
|
-
Args:
|
|
31922
|
-
- filters (string, optional): ElasticSearch query filter
|
|
31923
|
-
- fields (string, optional): Comma-separated field names
|
|
31924
|
-
- limit (number): Records to return, 1-10000 (default: 20)
|
|
31925
|
-
- offset (number): Pagination offset (default: 0)
|
|
31926
|
-
- sort_by (string, optional): Field to sort by
|
|
31927
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
31928
|
-
|
|
31929
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and institution records.`,
|
|
32054
|
+
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.",
|
|
31930
32055
|
inputSchema: CommonQuerySchema,
|
|
32056
|
+
outputSchema: FdicInstitutionsSearchOutputSchema,
|
|
31931
32057
|
annotations: {
|
|
31932
32058
|
readOnlyHint: true,
|
|
31933
32059
|
destructiveHint: false,
|
|
@@ -31944,7 +32070,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
31944
32070
|
params.offset ?? 0,
|
|
31945
32071
|
records.length
|
|
31946
32072
|
);
|
|
31947
|
-
const output =
|
|
32073
|
+
const output = capStructuredContent(
|
|
32074
|
+
{ ...pagination, institutions: records },
|
|
32075
|
+
"institutions"
|
|
32076
|
+
);
|
|
31948
32077
|
const text = truncateIfNeeded(
|
|
31949
32078
|
formatSearchResultText("institutions", records, pagination, [
|
|
31950
32079
|
"CERT",
|
|
@@ -31970,16 +32099,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
31970
32099
|
"fdic_get_institution",
|
|
31971
32100
|
{
|
|
31972
32101
|
title: "Get Institution by Certificate Number",
|
|
31973
|
-
description:
|
|
31974
|
-
|
|
31975
|
-
Use this when you know the exact CERT number for an institution. To find a CERT number, use fdic_search_institutions first.
|
|
31976
|
-
|
|
31977
|
-
Args:
|
|
31978
|
-
- cert (number): FDIC Certificate Number (e.g., 3511 for Bank of America)
|
|
31979
|
-
- fields (string, optional): Comma-separated list of fields to return
|
|
31980
|
-
|
|
31981
|
-
Returns a detailed institution profile suitable for concise summaries, with structured fields available for exact values when needed.`,
|
|
32102
|
+
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.",
|
|
31982
32103
|
inputSchema: CertSchema,
|
|
32104
|
+
outputSchema: FdicInstitutionLookupOutputSchema,
|
|
31983
32105
|
annotations: {
|
|
31984
32106
|
readOnlyHint: true,
|
|
31985
32107
|
destructiveHint: false,
|
|
@@ -32034,43 +32156,9 @@ function registerFailureTools(server) {
|
|
|
32034
32156
|
"fdic_search_failures",
|
|
32035
32157
|
{
|
|
32036
32158
|
title: "Search Bank Failures",
|
|
32037
|
-
description:
|
|
32038
|
-
|
|
32039
|
-
Returns data on bank failures including failure date, resolution type, estimated cost to the FDIC Deposit Insurance Fund, and acquiring institution info.
|
|
32040
|
-
|
|
32041
|
-
Common filter examples:
|
|
32042
|
-
- By state: STALP:CA (two-letter state code)
|
|
32043
|
-
- By year range: FAILDATE:[2008-01-01 TO 2010-12-31]
|
|
32044
|
-
- Recent failures: FAILDATE:[2020-01-01 TO *]
|
|
32045
|
-
- By resolution type: RESTYPE:PAYOFF or RESTYPE:"PURCHASE AND ASSUMPTION"
|
|
32046
|
-
- Large failures by cost: COST:[100000 TO *] (cost in $thousands)
|
|
32047
|
-
- By name: NAME:"Washington Mutual"
|
|
32048
|
-
|
|
32049
|
-
Resolution types (RESTYPE):
|
|
32050
|
-
PAYOFF = depositors paid directly, no acquirer
|
|
32051
|
-
PURCHASE AND ASSUMPTION = acquirer buys assets and assumes deposits
|
|
32052
|
-
PAYOUT = variant of payoff with insured-deposit transfer
|
|
32053
|
-
|
|
32054
|
-
Key returned fields:
|
|
32055
|
-
- CERT: FDIC Certificate Number
|
|
32056
|
-
- NAME: Institution name
|
|
32057
|
-
- CITY, STALP (two-letter state code), STNAME (full state name): Location
|
|
32058
|
-
- FAILDATE: Date of failure (YYYY-MM-DD)
|
|
32059
|
-
- SAVR: Savings association flag (SA) or bank (BK)
|
|
32060
|
-
- RESTYPE: Resolution type (see above)
|
|
32061
|
-
- QBFASSET: Total assets at failure ($thousands)
|
|
32062
|
-
- COST: Estimated cost to FDIC Deposit Insurance Fund ($thousands)
|
|
32063
|
-
|
|
32064
|
-
Args:
|
|
32065
|
-
- filters (string, optional): ElasticSearch query filter
|
|
32066
|
-
- fields (string, optional): Comma-separated field names
|
|
32067
|
-
- limit (number): Records to return (default: 20)
|
|
32068
|
-
- offset (number): Pagination offset (default: 0)
|
|
32069
|
-
- sort_by (string, optional): Field to sort by (e.g., FAILDATE, COST)
|
|
32070
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32071
|
-
|
|
32072
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and failure records.`,
|
|
32159
|
+
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.",
|
|
32073
32160
|
inputSchema: CommonQuerySchema,
|
|
32161
|
+
outputSchema: FdicFailuresSearchOutputSchema,
|
|
32074
32162
|
annotations: {
|
|
32075
32163
|
readOnlyHint: true,
|
|
32076
32164
|
destructiveHint: false,
|
|
@@ -32087,7 +32175,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32087
32175
|
params.offset ?? 0,
|
|
32088
32176
|
records.length
|
|
32089
32177
|
);
|
|
32090
|
-
const output =
|
|
32178
|
+
const output = capStructuredContent(
|
|
32179
|
+
{ ...pagination, failures: records },
|
|
32180
|
+
"failures"
|
|
32181
|
+
);
|
|
32091
32182
|
const text = truncateIfNeeded(
|
|
32092
32183
|
formatSearchResultText("failures", records, pagination, [
|
|
32093
32184
|
"CERT",
|
|
@@ -32114,16 +32205,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32114
32205
|
"fdic_get_institution_failure",
|
|
32115
32206
|
{
|
|
32116
32207
|
title: "Get Failure Details by Certificate Number",
|
|
32117
|
-
description:
|
|
32118
|
-
|
|
32119
|
-
Use this when you know the CERT of a failed institution to get its specific failure record.
|
|
32120
|
-
|
|
32121
|
-
Args:
|
|
32122
|
-
- cert (number): FDIC Certificate Number of the failed institution
|
|
32123
|
-
- fields (string, optional): Comma-separated list of fields to return
|
|
32124
|
-
|
|
32125
|
-
Returns detailed failure information suitable for concise summaries, with structured fields available for exact values when needed.`,
|
|
32208
|
+
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.",
|
|
32126
32209
|
inputSchema: CertSchema,
|
|
32210
|
+
outputSchema: FdicFailureLookupOutputSchema,
|
|
32127
32211
|
annotations: {
|
|
32128
32212
|
readOnlyHint: true,
|
|
32129
32213
|
destructiveHint: false,
|
|
@@ -32173,7 +32257,7 @@ Returns detailed failure information suitable for concise summaries, with struct
|
|
|
32173
32257
|
}
|
|
32174
32258
|
|
|
32175
32259
|
// src/tools/locations.ts
|
|
32176
|
-
var
|
|
32260
|
+
var import_zod3 = require("zod");
|
|
32177
32261
|
|
|
32178
32262
|
// src/tools/shared/queryUtils.ts
|
|
32179
32263
|
var CHUNK_SIZE = 25;
|
|
@@ -32269,7 +32353,7 @@ async function mapWithConcurrency(values, limit, mapper) {
|
|
|
32269
32353
|
|
|
32270
32354
|
// src/tools/locations.ts
|
|
32271
32355
|
var LocationQuerySchema = CommonQuerySchema.extend({
|
|
32272
|
-
cert:
|
|
32356
|
+
cert: import_zod3.z.number().int().positive().optional().describe(
|
|
32273
32357
|
"Filter by FDIC Certificate Number to get all branches of a specific institution"
|
|
32274
32358
|
)
|
|
32275
32359
|
});
|
|
@@ -32278,53 +32362,9 @@ function registerLocationTools(server) {
|
|
|
32278
32362
|
"fdic_search_locations",
|
|
32279
32363
|
{
|
|
32280
32364
|
title: "Search Institution Locations / Branches",
|
|
32281
|
-
description:
|
|
32282
|
-
|
|
32283
|
-
Returns branch/office data including address, city, state, coordinates, branch type, and establishment date.
|
|
32284
|
-
|
|
32285
|
-
Common filter examples:
|
|
32286
|
-
- All branches of a bank: CERT:3511
|
|
32287
|
-
- By state: STALP:TX (two-letter state code)
|
|
32288
|
-
- By city: CITY:"Austin"
|
|
32289
|
-
- Main offices only: BRNUM:0
|
|
32290
|
-
- By county: COUNTY:"Travis"
|
|
32291
|
-
- Active branches only: ENDEFYMD:[9999-01-01 TO *] (sentinel date 9999-12-31 means still open)
|
|
32292
|
-
- By metro area (CBSA): CBSA_METRO_NAME:"New York-Newark-Jersey City"
|
|
32293
|
-
|
|
32294
|
-
Branch service types (BRSERTYP):
|
|
32295
|
-
11 = Full service brick and mortar
|
|
32296
|
-
12 = Full service retail
|
|
32297
|
-
21 = Limited service administrative
|
|
32298
|
-
22 = Limited service military
|
|
32299
|
-
23 = Limited service drive-through
|
|
32300
|
-
24 = Limited service loan production
|
|
32301
|
-
25 = Limited service consumer/trust
|
|
32302
|
-
26 = Limited service Internet/mobile
|
|
32303
|
-
29 = Limited service other
|
|
32304
|
-
|
|
32305
|
-
Key returned fields:
|
|
32306
|
-
- CERT: FDIC Certificate Number
|
|
32307
|
-
- UNINAME: Institution name
|
|
32308
|
-
- NAMEFULL: Full branch name
|
|
32309
|
-
- ADDRESS, CITY, STALP (two-letter state code), ZIP: Branch address
|
|
32310
|
-
- COUNTY: County name
|
|
32311
|
-
- BRNUM: Branch number (0 = main office)
|
|
32312
|
-
- BRSERTYP: Branch service type code (see above)
|
|
32313
|
-
- LATITUDE, LONGITUDE: Geographic coordinates
|
|
32314
|
-
- ESTYMD: Branch established date (YYYY-MM-DD)
|
|
32315
|
-
- ENDEFYMD: Branch end date (9999-12-31 if still active)
|
|
32316
|
-
|
|
32317
|
-
Args:
|
|
32318
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32319
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32320
|
-
- fields (string, optional): Comma-separated field names
|
|
32321
|
-
- limit (number): Records to return (default: 20)
|
|
32322
|
-
- offset (number): Pagination offset (default: 0)
|
|
32323
|
-
- sort_by (string, optional): Field to sort by
|
|
32324
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32325
|
-
|
|
32326
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and branch location records.`,
|
|
32365
|
+
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.",
|
|
32327
32366
|
inputSchema: LocationQuerySchema,
|
|
32367
|
+
outputSchema: FdicLocationsSearchOutputSchema,
|
|
32328
32368
|
annotations: {
|
|
32329
32369
|
readOnlyHint: true,
|
|
32330
32370
|
destructiveHint: false,
|
|
@@ -32348,7 +32388,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32348
32388
|
params.offset ?? 0,
|
|
32349
32389
|
records.length
|
|
32350
32390
|
);
|
|
32351
|
-
const output =
|
|
32391
|
+
const output = capStructuredContent(
|
|
32392
|
+
{ ...pagination, locations: records },
|
|
32393
|
+
"locations"
|
|
32394
|
+
);
|
|
32352
32395
|
const text = truncateIfNeeded(
|
|
32353
32396
|
formatSearchResultText("locations", records, pagination, [
|
|
32354
32397
|
"CERT",
|
|
@@ -32373,9 +32416,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32373
32416
|
}
|
|
32374
32417
|
|
|
32375
32418
|
// src/tools/history.ts
|
|
32376
|
-
var
|
|
32419
|
+
var import_zod4 = require("zod");
|
|
32377
32420
|
var HistoryQuerySchema = CommonQuerySchema.extend({
|
|
32378
|
-
cert:
|
|
32421
|
+
cert: import_zod4.z.number().int().positive().optional().describe(
|
|
32379
32422
|
"Filter by FDIC Certificate Number to get history for a specific institution"
|
|
32380
32423
|
)
|
|
32381
32424
|
});
|
|
@@ -32384,57 +32427,9 @@ function registerHistoryTools(server) {
|
|
|
32384
32427
|
"fdic_search_history",
|
|
32385
32428
|
{
|
|
32386
32429
|
title: "Search Institution History / Structure Changes",
|
|
32387
|
-
description:
|
|
32388
|
-
|
|
32389
|
-
Returns records on mergers, acquisitions, name changes, charter conversions, failures, and other significant structural events.
|
|
32390
|
-
|
|
32391
|
-
Common filter examples:
|
|
32392
|
-
- History for a specific bank: CERT:3511
|
|
32393
|
-
- Mergers: TYPE:merger
|
|
32394
|
-
- Failures: TYPE:failure
|
|
32395
|
-
- Name changes: CHANGECODE:CO
|
|
32396
|
-
- By date range: PROCDATE:[2008-01-01 TO 2009-12-31]
|
|
32397
|
-
- By state: PSTALP:CA (two-letter state code)
|
|
32398
|
-
|
|
32399
|
-
Event types (TYPE):
|
|
32400
|
-
merger = institution was merged into another
|
|
32401
|
-
failure = institution failed
|
|
32402
|
-
assistance = received FDIC assistance transaction
|
|
32403
|
-
insurance = insurance-related event (new coverage, termination)
|
|
32404
|
-
|
|
32405
|
-
Common change codes (CHANGECODE):
|
|
32406
|
-
CO = name change
|
|
32407
|
-
CR = charter conversion
|
|
32408
|
-
DC = deposit assumption change
|
|
32409
|
-
MA = merger/acquisition (absorbed by another institution)
|
|
32410
|
-
NI = new institution insured
|
|
32411
|
-
TC = trust company conversion
|
|
32412
|
-
|
|
32413
|
-
Key returned fields:
|
|
32414
|
-
- CERT: FDIC Certificate Number
|
|
32415
|
-
- INSTNAME: Institution name
|
|
32416
|
-
- CLASS: Charter class at time of change
|
|
32417
|
-
- PCITY, PSTALP: Location (city, two-letter state code)
|
|
32418
|
-
- PROCDATE: Processing date of the change (YYYY-MM-DD)
|
|
32419
|
-
- EFFDATE: Effective date of the change (YYYY-MM-DD)
|
|
32420
|
-
- ENDEFYMD: End effective date
|
|
32421
|
-
- PCERT: Predecessor/successor CERT (for mergers)
|
|
32422
|
-
- TYPE: Type of structural change (see above)
|
|
32423
|
-
- CHANGECODE: Code for type of change (see above)
|
|
32424
|
-
- CHANGECODE_DESC: Human-readable description of the change code
|
|
32425
|
-
- INSDATE: Insurance date
|
|
32426
|
-
|
|
32427
|
-
Args:
|
|
32428
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32429
|
-
- filters (string, optional): ElasticSearch query filters
|
|
32430
|
-
- fields (string, optional): Comma-separated field names
|
|
32431
|
-
- limit (number): Records to return (default: 20)
|
|
32432
|
-
- offset (number): Pagination offset (default: 0)
|
|
32433
|
-
- sort_by (string, optional): Field to sort by (e.g., PROCDATE)
|
|
32434
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32435
|
-
|
|
32436
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and event records.`,
|
|
32430
|
+
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.",
|
|
32437
32431
|
inputSchema: HistoryQuerySchema,
|
|
32432
|
+
outputSchema: FdicHistorySearchOutputSchema,
|
|
32438
32433
|
annotations: {
|
|
32439
32434
|
readOnlyHint: true,
|
|
32440
32435
|
destructiveHint: false,
|
|
@@ -32458,7 +32453,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32458
32453
|
params.offset ?? 0,
|
|
32459
32454
|
records.length
|
|
32460
32455
|
);
|
|
32461
|
-
const output =
|
|
32456
|
+
const output = capStructuredContent(
|
|
32457
|
+
{ ...pagination, events: records },
|
|
32458
|
+
"events"
|
|
32459
|
+
);
|
|
32462
32460
|
const text = truncateIfNeeded(
|
|
32463
32461
|
formatSearchResultText("events", records, pagination, [
|
|
32464
32462
|
"CERT",
|
|
@@ -32483,63 +32481,30 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32483
32481
|
}
|
|
32484
32482
|
|
|
32485
32483
|
// src/tools/financials.ts
|
|
32486
|
-
var
|
|
32484
|
+
var import_zod5 = require("zod");
|
|
32487
32485
|
var FinancialQuerySchema = CommonQuerySchema.extend({
|
|
32488
|
-
sort_order:
|
|
32486
|
+
sort_order: import_zod5.z.enum(["ASC", "DESC"]).default("DESC").describe(
|
|
32489
32487
|
"Sort direction: DESC (descending, default for most recent first) or ASC (ascending)"
|
|
32490
32488
|
),
|
|
32491
|
-
cert:
|
|
32489
|
+
cert: import_zod5.z.number().int().positive().optional().describe(
|
|
32492
32490
|
"Filter by FDIC Certificate Number to get financials for a specific institution"
|
|
32493
32491
|
),
|
|
32494
|
-
repdte:
|
|
32495
|
-
"Filter by Report Date (REPDTE) in YYYYMMDD format
|
|
32492
|
+
repdte: import_zod5.z.string().optional().describe(
|
|
32493
|
+
"Filter by Report Date (REPDTE) in YYYYMMDD format (quarter-end: 0331, 0630, 0930, 1231). If omitted, returns all available dates (sorted most recent first)."
|
|
32496
32494
|
)
|
|
32497
32495
|
});
|
|
32498
32496
|
var SummaryQuerySchema = CommonQuerySchema.extend({
|
|
32499
|
-
cert:
|
|
32500
|
-
year:
|
|
32497
|
+
cert: import_zod5.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32498
|
+
year: import_zod5.z.number().int().min(1934).optional().describe("Filter by specific year (e.g., 2022)")
|
|
32501
32499
|
});
|
|
32502
32500
|
function registerFinancialTools(server) {
|
|
32503
32501
|
server.registerTool(
|
|
32504
32502
|
"fdic_search_financials",
|
|
32505
32503
|
{
|
|
32506
32504
|
title: "Search Institution Financial Data",
|
|
32507
|
-
description:
|
|
32508
|
-
|
|
32509
|
-
Returns balance sheet, income statement, capital, and performance ratio data from FDIC Call Reports.
|
|
32510
|
-
|
|
32511
|
-
Common filter examples:
|
|
32512
|
-
- Financials for a specific bank: CERT:3511
|
|
32513
|
-
- By report date: REPDTE:20231231
|
|
32514
|
-
- High-profit banks in Q4 2023: REPDTE:20231231 AND ROA:[1.5 TO *]
|
|
32515
|
-
- Large banks most recent: ASSET:[10000000 TO *]
|
|
32516
|
-
- Negative net income: NETINC:[* TO 0]
|
|
32517
|
-
|
|
32518
|
-
Key returned fields:
|
|
32519
|
-
- CERT: FDIC Certificate Number
|
|
32520
|
-
- REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
|
|
32521
|
-
- ASSET: Total assets ($thousands)
|
|
32522
|
-
- DEP: Total deposits ($thousands)
|
|
32523
|
-
- DEPDOM: Domestic deposits ($thousands)
|
|
32524
|
-
- INTINC: Total interest income ($thousands)
|
|
32525
|
-
- EINTEXP: Total interest expense ($thousands)
|
|
32526
|
-
- NETINC: Net income ($thousands)
|
|
32527
|
-
- ROA: Return on assets (%)
|
|
32528
|
-
- ROE: Return on equity (%)
|
|
32529
|
-
- NETNIM: Net interest margin (%)
|
|
32530
|
-
|
|
32531
|
-
Args:
|
|
32532
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32533
|
-
- repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
|
|
32534
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32535
|
-
- fields (string, optional): Comma-separated field names (the full set has 1,100+ fields)
|
|
32536
|
-
- limit (number): Records to return (default: 20)
|
|
32537
|
-
- offset (number): Pagination offset (default: 0)
|
|
32538
|
-
- sort_by (string, optional): Field to sort by
|
|
32539
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'DESC' recommended for most recent first)
|
|
32540
|
-
|
|
32541
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and quarterly financial records.`,
|
|
32505
|
+
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.",
|
|
32542
32506
|
inputSchema: FinancialQuerySchema,
|
|
32507
|
+
outputSchema: FdicFinancialsSearchOutputSchema,
|
|
32543
32508
|
annotations: {
|
|
32544
32509
|
readOnlyHint: true,
|
|
32545
32510
|
destructiveHint: false,
|
|
@@ -32564,7 +32529,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32564
32529
|
params.offset ?? 0,
|
|
32565
32530
|
records.length
|
|
32566
32531
|
);
|
|
32567
|
-
const output =
|
|
32532
|
+
const output = capStructuredContent(
|
|
32533
|
+
{ ...pagination, financials: records },
|
|
32534
|
+
"financials"
|
|
32535
|
+
);
|
|
32568
32536
|
const text = truncateIfNeeded(
|
|
32569
32537
|
formatSearchResultText("financial records", records, pagination, [
|
|
32570
32538
|
"CERT",
|
|
@@ -32590,40 +32558,9 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32590
32558
|
"fdic_search_summary",
|
|
32591
32559
|
{
|
|
32592
32560
|
title: "Search Annual Financial Summary Data",
|
|
32593
|
-
description:
|
|
32594
|
-
|
|
32595
|
-
Returns annual snapshots of key financial metrics \u2014 useful for tracking an institution's growth over time.
|
|
32596
|
-
|
|
32597
|
-
Common filter examples:
|
|
32598
|
-
- Annual history for a bank: CERT:3511
|
|
32599
|
-
- Specific year: YEAR:2022
|
|
32600
|
-
- Year range: YEAR:[2010 TO 2020]
|
|
32601
|
-
- Large banks in 2022: YEAR:2022 AND ASSET:[10000000 TO *]
|
|
32602
|
-
- Profitable in 2023: YEAR:2023 AND ROE:[10 TO *]
|
|
32603
|
-
|
|
32604
|
-
Key returned fields:
|
|
32605
|
-
- CERT: FDIC Certificate Number
|
|
32606
|
-
- YEAR: Report year
|
|
32607
|
-
- ASSET: Total assets ($thousands)
|
|
32608
|
-
- DEP: Total deposits ($thousands)
|
|
32609
|
-
- NETINC: Net income ($thousands)
|
|
32610
|
-
- ROA: Return on assets (%)
|
|
32611
|
-
- ROE: Return on equity (%)
|
|
32612
|
-
- OFFICES: Number of branch offices
|
|
32613
|
-
- REPDTE: Report Date \u2014 the last day of the reporting period (YYYYMMDD)
|
|
32614
|
-
|
|
32615
|
-
Args:
|
|
32616
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32617
|
-
- year (number, optional): Filter by specific year (1934-present)
|
|
32618
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32619
|
-
- fields (string, optional): Comma-separated field names
|
|
32620
|
-
- limit (number): Records to return (default: 20)
|
|
32621
|
-
- offset (number): Pagination offset (default: 0)
|
|
32622
|
-
- sort_by (string, optional): Field to sort by (e.g., YEAR, ASSET)
|
|
32623
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32624
|
-
|
|
32625
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and annual summary records.`,
|
|
32561
|
+
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.",
|
|
32626
32562
|
inputSchema: SummaryQuerySchema,
|
|
32563
|
+
outputSchema: FdicSummarySearchOutputSchema,
|
|
32627
32564
|
annotations: {
|
|
32628
32565
|
readOnlyHint: true,
|
|
32629
32566
|
destructiveHint: false,
|
|
@@ -32648,7 +32585,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32648
32585
|
params.offset ?? 0,
|
|
32649
32586
|
records.length
|
|
32650
32587
|
);
|
|
32651
|
-
const output =
|
|
32588
|
+
const output = capStructuredContent(
|
|
32589
|
+
{ ...pagination, summary: records },
|
|
32590
|
+
"summary"
|
|
32591
|
+
);
|
|
32652
32592
|
const text = truncateIfNeeded(
|
|
32653
32593
|
formatSearchResultText("annual summary records", records, pagination, [
|
|
32654
32594
|
"CERT",
|
|
@@ -32673,10 +32613,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32673
32613
|
}
|
|
32674
32614
|
|
|
32675
32615
|
// src/tools/sod.ts
|
|
32676
|
-
var
|
|
32616
|
+
var import_zod6 = require("zod");
|
|
32677
32617
|
var SodQuerySchema = CommonQuerySchema.extend({
|
|
32678
|
-
cert:
|
|
32679
|
-
year:
|
|
32618
|
+
cert: import_zod6.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32619
|
+
year: import_zod6.z.number().int().min(1994).optional().describe(
|
|
32680
32620
|
"Filter by specific year (1994-present). SOD data is annual."
|
|
32681
32621
|
)
|
|
32682
32622
|
});
|
|
@@ -32685,40 +32625,9 @@ function registerSodTools(server) {
|
|
|
32685
32625
|
"fdic_search_sod",
|
|
32686
32626
|
{
|
|
32687
32627
|
title: "Search Summary of Deposits (SOD)",
|
|
32688
|
-
description:
|
|
32689
|
-
|
|
32690
|
-
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.
|
|
32691
|
-
|
|
32692
|
-
Common filter examples:
|
|
32693
|
-
- All branches for a bank: CERT:3511
|
|
32694
|
-
- SOD for specific year: YEAR:2022
|
|
32695
|
-
- Branches in a state: STALPBR:CA
|
|
32696
|
-
- Branches in a city: CITYBR:"Austin"
|
|
32697
|
-
- High-deposit branches: DEPSUMBR:[1000000 TO *]
|
|
32698
|
-
- By metro area (MSA code): MSABR:19100
|
|
32699
|
-
|
|
32700
|
-
Key returned fields:
|
|
32701
|
-
- YEAR: Report year (as of June 30)
|
|
32702
|
-
- CERT: FDIC Certificate Number
|
|
32703
|
-
- BRNUM: Branch number (0 = main office)
|
|
32704
|
-
- NAMEFULL: Branch or institution name
|
|
32705
|
-
- ADDRESBR, CITYBR, STALPBR, ZIPBR: Branch address
|
|
32706
|
-
- DEPSUMBR: Total deposits at branch ($thousands)
|
|
32707
|
-
- MSABR: Metropolitan Statistical Area code (numeric; 0 = non-MSA)
|
|
32708
|
-
- LATITUDE, LONGITUDE: Coordinates
|
|
32709
|
-
|
|
32710
|
-
Args:
|
|
32711
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32712
|
-
- year (number, optional): SOD report year (1994-present)
|
|
32713
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32714
|
-
- fields (string, optional): Comma-separated field names
|
|
32715
|
-
- limit (number): Records to return (default: 20)
|
|
32716
|
-
- offset (number): Pagination offset (default: 0)
|
|
32717
|
-
- sort_by (string, optional): Field to sort by (e.g., DEPSUMBR, YEAR)
|
|
32718
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32719
|
-
|
|
32720
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and deposit records.`,
|
|
32628
|
+
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.",
|
|
32721
32629
|
inputSchema: SodQuerySchema,
|
|
32630
|
+
outputSchema: FdicSodSearchOutputSchema,
|
|
32722
32631
|
annotations: {
|
|
32723
32632
|
readOnlyHint: true,
|
|
32724
32633
|
destructiveHint: false,
|
|
@@ -32743,7 +32652,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32743
32652
|
params.offset ?? 0,
|
|
32744
32653
|
records.length
|
|
32745
32654
|
);
|
|
32746
|
-
const output =
|
|
32655
|
+
const output = capStructuredContent(
|
|
32656
|
+
{ ...pagination, deposits: records },
|
|
32657
|
+
"deposits"
|
|
32658
|
+
);
|
|
32747
32659
|
const text = truncateIfNeeded(
|
|
32748
32660
|
formatSearchResultText("deposit records", records, pagination, [
|
|
32749
32661
|
"CERT",
|
|
@@ -32768,11 +32680,11 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32768
32680
|
}
|
|
32769
32681
|
|
|
32770
32682
|
// src/tools/demographics.ts
|
|
32771
|
-
var
|
|
32683
|
+
var import_zod7 = require("zod");
|
|
32772
32684
|
var DemographicsQuerySchema = CommonQuerySchema.extend({
|
|
32773
|
-
cert:
|
|
32774
|
-
repdte:
|
|
32775
|
-
"Filter by Report Date (REPDTE) in YYYYMMDD format
|
|
32685
|
+
cert: import_zod7.z.number().int().positive().optional().describe("Filter by FDIC Certificate Number"),
|
|
32686
|
+
repdte: import_zod7.z.string().optional().describe(
|
|
32687
|
+
"Filter by Report Date (REPDTE) in YYYYMMDD format (quarter-end: 0331, 0630, 0930, 1231)."
|
|
32776
32688
|
)
|
|
32777
32689
|
});
|
|
32778
32690
|
function registerDemographicsTools(server) {
|
|
@@ -32780,43 +32692,9 @@ function registerDemographicsTools(server) {
|
|
|
32780
32692
|
"fdic_search_demographics",
|
|
32781
32693
|
{
|
|
32782
32694
|
title: "Search Institution Demographics Data",
|
|
32783
|
-
description:
|
|
32784
|
-
|
|
32785
|
-
Returns quarterly demographic and market-structure attributes such as office counts, territory assignments, metro classification, county/country codes, and selected geographic reference data.
|
|
32786
|
-
|
|
32787
|
-
Common filter examples:
|
|
32788
|
-
- Demographics for a specific bank: CERT:3511
|
|
32789
|
-
- By report date: REPDTE:20251231
|
|
32790
|
-
- Institutions in metro areas: METRO:1
|
|
32791
|
-
- Institutions with out-of-state offices: OFFSTATE:[1 TO *]
|
|
32792
|
-
- Minority status date present: MNRTYDTE:[19000101 TO 99991231]
|
|
32793
|
-
|
|
32794
|
-
Key returned fields:
|
|
32795
|
-
- CERT: FDIC Certificate Number
|
|
32796
|
-
- REPDTE: Report Date \u2014 the last day of the quarterly reporting period (YYYYMMDD)
|
|
32797
|
-
- QTRNO: Quarter number
|
|
32798
|
-
- OFFTOT: Total offices
|
|
32799
|
-
- OFFSTATE: Offices in other states
|
|
32800
|
-
- OFFNDOM: Offices in non-domestic territories
|
|
32801
|
-
- OFFOTH: Other offices
|
|
32802
|
-
- OFFSOD: Offices included in Summary of Deposits
|
|
32803
|
-
- METRO, MICRO: Metro/micro area flags
|
|
32804
|
-
- CBSANAME, CSA: Core-based statistical area data
|
|
32805
|
-
- FDICTERR, RISKTERR: FDIC and risk territory assignments
|
|
32806
|
-
- SIMS_LAT, SIMS_LONG: Geographic coordinates
|
|
32807
|
-
|
|
32808
|
-
Args:
|
|
32809
|
-
- cert (number, optional): Filter by institution CERT number
|
|
32810
|
-
- repdte (string, optional): Report Date in YYYYMMDD format (quarter-end dates: 0331, 0630, 0930, 1231)
|
|
32811
|
-
- filters (string, optional): Additional ElasticSearch query filters
|
|
32812
|
-
- fields (string, optional): Comma-separated field names
|
|
32813
|
-
- limit (number): Records to return (default: 20)
|
|
32814
|
-
- offset (number): Pagination offset (default: 0)
|
|
32815
|
-
- sort_by (string, optional): Field to sort by
|
|
32816
|
-
- sort_order ('ASC'|'DESC'): Sort direction (default: 'ASC')
|
|
32817
|
-
|
|
32818
|
-
Prefer concise human-readable summaries or tables when answering users. Structured fields are available for totals, pagination, and demographic records.`,
|
|
32695
|
+
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.",
|
|
32819
32696
|
inputSchema: DemographicsQuerySchema,
|
|
32697
|
+
outputSchema: FdicDemographicsSearchOutputSchema,
|
|
32820
32698
|
annotations: {
|
|
32821
32699
|
readOnlyHint: true,
|
|
32822
32700
|
destructiveHint: false,
|
|
@@ -32841,7 +32719,10 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32841
32719
|
params.offset ?? 0,
|
|
32842
32720
|
records.length
|
|
32843
32721
|
);
|
|
32844
|
-
const output =
|
|
32722
|
+
const output = capStructuredContent(
|
|
32723
|
+
{ ...pagination, demographics: records },
|
|
32724
|
+
"demographics"
|
|
32725
|
+
);
|
|
32845
32726
|
const text = truncateIfNeeded(
|
|
32846
32727
|
formatSearchResultText("demographic records", records, pagination, [
|
|
32847
32728
|
"CERT",
|
|
@@ -32866,7 +32747,7 @@ Prefer concise human-readable summaries or tables when answering users. Structur
|
|
|
32866
32747
|
}
|
|
32867
32748
|
|
|
32868
32749
|
// src/tools/analysis.ts
|
|
32869
|
-
var
|
|
32750
|
+
var import_zod8 = require("zod");
|
|
32870
32751
|
|
|
32871
32752
|
// src/tools/shared/progress.ts
|
|
32872
32753
|
function asProgressToken(value) {
|
|
@@ -32892,7 +32773,7 @@ async function sendProgressNotification(sender, progressToken, progress, message
|
|
|
32892
32773
|
}
|
|
32893
32774
|
|
|
32894
32775
|
// src/tools/analysis.ts
|
|
32895
|
-
var SortFieldSchema =
|
|
32776
|
+
var SortFieldSchema = import_zod8.z.enum([
|
|
32896
32777
|
"asset_growth",
|
|
32897
32778
|
"asset_growth_pct",
|
|
32898
32779
|
"dep_growth",
|
|
@@ -32906,35 +32787,35 @@ var SortFieldSchema = import_zod7.z.enum([
|
|
|
32906
32787
|
"deposits_per_office_change",
|
|
32907
32788
|
"deposits_to_assets_change"
|
|
32908
32789
|
]);
|
|
32909
|
-
var AnalysisModeSchema =
|
|
32910
|
-
var SnapshotAnalysisSchema =
|
|
32911
|
-
state:
|
|
32790
|
+
var AnalysisModeSchema = import_zod8.z.enum(["snapshot", "timeseries"]);
|
|
32791
|
+
var SnapshotAnalysisSchema = import_zod8.z.object({
|
|
32792
|
+
state: import_zod8.z.string().optional().describe(
|
|
32912
32793
|
'State name for the institution roster filter. Example: "North Carolina"'
|
|
32913
32794
|
),
|
|
32914
|
-
certs:
|
|
32795
|
+
certs: import_zod8.z.array(import_zod8.z.number().int().positive()).max(100).optional().describe(
|
|
32915
32796
|
"Optional list of FDIC certificate numbers to compare directly. Max 100."
|
|
32916
32797
|
),
|
|
32917
|
-
institution_filters:
|
|
32798
|
+
institution_filters: import_zod8.z.string().optional().describe(
|
|
32918
32799
|
'Additional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"'
|
|
32919
32800
|
),
|
|
32920
|
-
active_only:
|
|
32921
|
-
start_repdte:
|
|
32801
|
+
active_only: import_zod8.z.boolean().default(true).describe("Limit the comparison set to currently active institutions."),
|
|
32802
|
+
start_repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
|
|
32922
32803
|
"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."
|
|
32923
32804
|
),
|
|
32924
|
-
end_repdte:
|
|
32805
|
+
end_repdte: import_zod8.z.string().regex(/^\d{8}$/).optional().describe(
|
|
32925
32806
|
"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)."
|
|
32926
32807
|
),
|
|
32927
32808
|
analysis_mode: AnalysisModeSchema.default("snapshot").describe(
|
|
32928
32809
|
"Use snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range."
|
|
32929
32810
|
),
|
|
32930
|
-
include_demographics:
|
|
32811
|
+
include_demographics: import_zod8.z.boolean().default(true).describe(
|
|
32931
32812
|
"Include office-count changes from the demographics dataset when available."
|
|
32932
32813
|
),
|
|
32933
|
-
limit:
|
|
32814
|
+
limit: import_zod8.z.number().int().min(1).max(100).default(10).describe("Maximum number of ranked comparisons to return."),
|
|
32934
32815
|
sort_by: SortFieldSchema.default("asset_growth").describe(
|
|
32935
32816
|
"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."
|
|
32936
32817
|
),
|
|
32937
|
-
sort_order:
|
|
32818
|
+
sort_order: import_zod8.z.enum(["ASC", "DESC"]).default("DESC").describe("Sort direction for the ranked comparisons.")
|
|
32938
32819
|
});
|
|
32939
32820
|
function resolveSnapshotDefaults(params) {
|
|
32940
32821
|
const end_repdte = params.end_repdte ?? getDefaultReportDate();
|
|
@@ -33485,6 +33366,7 @@ Inputs:
|
|
|
33485
33366
|
|
|
33486
33367
|
Returns concise comparison text plus structured deltas, derived metrics, and insight tags for each institution.`,
|
|
33487
33368
|
inputSchema: SnapshotAnalysisSchema,
|
|
33369
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
33488
33370
|
annotations: {
|
|
33489
33371
|
readOnlyHint: true,
|
|
33490
33372
|
destructiveHint: false,
|
|
@@ -33738,7 +33620,7 @@ Returns concise comparison text plus structured deltas, derived metrics, and ins
|
|
|
33738
33620
|
}
|
|
33739
33621
|
|
|
33740
33622
|
// src/tools/peerGroup.ts
|
|
33741
|
-
var
|
|
33623
|
+
var import_zod9 = require("zod");
|
|
33742
33624
|
|
|
33743
33625
|
// src/tools/shared/financialMetrics.ts
|
|
33744
33626
|
function safeRatio(numerator, denominator) {
|
|
@@ -33882,33 +33764,33 @@ var METRIC_DEFINITIONS = {
|
|
|
33882
33764
|
label: "Non-Interest Income Share"
|
|
33883
33765
|
}
|
|
33884
33766
|
};
|
|
33885
|
-
var PeerGroupInputSchema =
|
|
33886
|
-
cert:
|
|
33767
|
+
var PeerGroupInputSchema = import_zod9.z.object({
|
|
33768
|
+
cert: import_zod9.z.number().int().positive().optional().describe(
|
|
33887
33769
|
"Subject institution CERT number. When provided, auto-derives peer criteria and ranks this bank against peers."
|
|
33888
33770
|
),
|
|
33889
|
-
repdte:
|
|
33771
|
+
repdte: import_zod9.z.string().regex(/^\d{8}$/).optional().describe(
|
|
33890
33772
|
"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)."
|
|
33891
33773
|
),
|
|
33892
|
-
asset_min:
|
|
33774
|
+
asset_min: import_zod9.z.number().positive().optional().describe(
|
|
33893
33775
|
"Minimum total assets ($thousands) for peer selection. Defaults to 50% of subject's report-date assets when cert is provided."
|
|
33894
33776
|
),
|
|
33895
|
-
asset_max:
|
|
33777
|
+
asset_max: import_zod9.z.number().positive().optional().describe(
|
|
33896
33778
|
"Maximum total assets ($thousands) for peer selection. Defaults to 200% of subject's report-date assets when cert is provided."
|
|
33897
33779
|
),
|
|
33898
|
-
charter_classes:
|
|
33780
|
+
charter_classes: import_zod9.z.array(import_zod9.z.string()).optional().describe(
|
|
33899
33781
|
`Charter class codes to include (e.g., ["N", "SM"]). Defaults to the subject's charter class when cert is provided.`
|
|
33900
33782
|
),
|
|
33901
|
-
state:
|
|
33902
|
-
raw_filter:
|
|
33783
|
+
state: import_zod9.z.string().regex(/^[A-Z]{2}$/).optional().describe('Two-letter state code (e.g., "NC", "TX").'),
|
|
33784
|
+
raw_filter: import_zod9.z.string().optional().describe(
|
|
33903
33785
|
"Advanced: raw ElasticSearch query string appended to peer selection criteria with AND."
|
|
33904
33786
|
),
|
|
33905
|
-
active_only:
|
|
33787
|
+
active_only: import_zod9.z.boolean().default(true).describe(
|
|
33906
33788
|
"Limit to institutions where ACTIVE:1 (currently operating, FDIC-insured)."
|
|
33907
33789
|
),
|
|
33908
|
-
extra_fields:
|
|
33790
|
+
extra_fields: import_zod9.z.array(import_zod9.z.string()).optional().describe(
|
|
33909
33791
|
"Additional FDIC field names to include as raw values in the response. Does not affect peer selection."
|
|
33910
33792
|
),
|
|
33911
|
-
limit:
|
|
33793
|
+
limit: import_zod9.z.number().int().min(1).max(500).default(50).describe(
|
|
33912
33794
|
"Max peer records returned in the response. All matched peers are used for ranking regardless of this limit."
|
|
33913
33795
|
)
|
|
33914
33796
|
});
|
|
@@ -34037,6 +33919,7 @@ Output includes:
|
|
|
34037
33919
|
|
|
34038
33920
|
Override precedence: cert derives defaults, then explicit params override them.`,
|
|
34039
33921
|
inputSchema: PeerGroupInputSchema,
|
|
33922
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
34040
33923
|
annotations: {
|
|
34041
33924
|
readOnlyHint: true,
|
|
34042
33925
|
destructiveHint: false,
|
|
@@ -34373,7 +34256,7 @@ Override precedence: cert derives defaults, then explicit params override them.`
|
|
|
34373
34256
|
}
|
|
34374
34257
|
|
|
34375
34258
|
// src/tools/bankHealth.ts
|
|
34376
|
-
var
|
|
34259
|
+
var import_zod10 = require("zod");
|
|
34377
34260
|
|
|
34378
34261
|
// src/tools/shared/camelsScoring.ts
|
|
34379
34262
|
var SCORING_RULES = {
|
|
@@ -35694,12 +35577,12 @@ function collectRiskSignals(metrics, components, trends) {
|
|
|
35694
35577
|
}
|
|
35695
35578
|
return signals;
|
|
35696
35579
|
}
|
|
35697
|
-
var BankHealthInputSchema =
|
|
35698
|
-
cert:
|
|
35699
|
-
repdte:
|
|
35580
|
+
var BankHealthInputSchema = import_zod10.z.object({
|
|
35581
|
+
cert: import_zod10.z.number().int().positive().describe("FDIC Certificate Number of the institution to analyze."),
|
|
35582
|
+
repdte: import_zod10.z.string().regex(/^\d{8}$/).optional().describe(
|
|
35700
35583
|
"Report Date (YYYYMMDD). Defaults to the most recent quarter likely to have published data."
|
|
35701
35584
|
),
|
|
35702
|
-
quarters:
|
|
35585
|
+
quarters: import_zod10.z.number().int().min(1).max(20).default(8).describe("Number of prior quarters to fetch for trend analysis (default 8).")
|
|
35703
35586
|
});
|
|
35704
35587
|
function registerBankHealthTools(server) {
|
|
35705
35588
|
server.registerTool(
|
|
@@ -35720,6 +35603,7 @@ Output includes:
|
|
|
35720
35603
|
|
|
35721
35604
|
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.`,
|
|
35722
35605
|
inputSchema: BankHealthInputSchema,
|
|
35606
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
35723
35607
|
annotations: {
|
|
35724
35608
|
readOnlyHint: true,
|
|
35725
35609
|
destructiveHint: false,
|
|
@@ -35861,7 +35745,7 @@ NOTE: Management (M) is omitted from component scoring \u2014 cannot be assessed
|
|
|
35861
35745
|
}
|
|
35862
35746
|
|
|
35863
35747
|
// src/tools/peerHealth.ts
|
|
35864
|
-
var
|
|
35748
|
+
var import_zod11 = require("zod");
|
|
35865
35749
|
|
|
35866
35750
|
// src/tools/shared/peerEngine.ts
|
|
35867
35751
|
function computeMedian2(values) {
|
|
@@ -35935,15 +35819,15 @@ function computePeerStats(subjectValue, peerValues, options) {
|
|
|
35935
35819
|
}
|
|
35936
35820
|
|
|
35937
35821
|
// src/tools/peerHealth.ts
|
|
35938
|
-
var PeerHealthInputSchema =
|
|
35939
|
-
cert:
|
|
35940
|
-
certs:
|
|
35941
|
-
state:
|
|
35942
|
-
asset_min:
|
|
35943
|
-
asset_max:
|
|
35944
|
-
repdte:
|
|
35945
|
-
sort_by:
|
|
35946
|
-
limit:
|
|
35822
|
+
var PeerHealthInputSchema = import_zod11.z.object({
|
|
35823
|
+
cert: import_zod11.z.number().int().positive().optional().describe("Subject institution CERT to highlight in the ranking. Optional."),
|
|
35824
|
+
certs: import_zod11.z.array(import_zod11.z.number().int().positive()).max(50).optional().describe("Explicit list of CERTs to compare (max 50)."),
|
|
35825
|
+
state: import_zod11.z.string().regex(/^[A-Z]{2}$/).optional().describe('Two-letter state code to select all active institutions (e.g., "WY").'),
|
|
35826
|
+
asset_min: import_zod11.z.number().positive().optional().describe("Minimum total assets ($thousands) for peer selection."),
|
|
35827
|
+
asset_max: import_zod11.z.number().positive().optional().describe("Maximum total assets ($thousands) for peer selection."),
|
|
35828
|
+
repdte: import_zod11.z.string().regex(/^\d{8}$/).optional().describe("Report Date (YYYYMMDD). Defaults to the most recent quarter."),
|
|
35829
|
+
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."),
|
|
35830
|
+
limit: import_zod11.z.number().int().min(1).max(100).default(25).describe("Max institutions to return in the response.")
|
|
35947
35831
|
});
|
|
35948
35832
|
function sortKeyToComponent(key) {
|
|
35949
35833
|
const map = {
|
|
@@ -35973,6 +35857,7 @@ Output: Ranked list with per-institution proxy_score (1-4 scale) and proxy_band,
|
|
|
35973
35857
|
|
|
35974
35858
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
35975
35859
|
inputSchema: PeerHealthInputSchema,
|
|
35860
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
35976
35861
|
annotations: {
|
|
35977
35862
|
readOnlyHint: true,
|
|
35978
35863
|
destructiveHint: false,
|
|
@@ -36342,7 +36227,7 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36342
36227
|
}
|
|
36343
36228
|
|
|
36344
36229
|
// src/tools/riskSignals.ts
|
|
36345
|
-
var
|
|
36230
|
+
var import_zod12 = require("zod");
|
|
36346
36231
|
function classifyRiskSignals(metrics, trends) {
|
|
36347
36232
|
const signals = [];
|
|
36348
36233
|
if (metrics.tier1_leverage !== null && metrics.tier1_leverage < 5) {
|
|
@@ -36413,15 +36298,15 @@ function classifyRiskSignals(metrics, trends) {
|
|
|
36413
36298
|
return signals;
|
|
36414
36299
|
}
|
|
36415
36300
|
var SEVERITY_ORDER = { critical: 0, warning: 1, info: 2 };
|
|
36416
|
-
var RiskSignalsInputSchema =
|
|
36417
|
-
state:
|
|
36418
|
-
certs:
|
|
36419
|
-
asset_min:
|
|
36420
|
-
asset_max:
|
|
36421
|
-
repdte:
|
|
36422
|
-
min_severity:
|
|
36423
|
-
quarters:
|
|
36424
|
-
limit:
|
|
36301
|
+
var RiskSignalsInputSchema = import_zod12.z.object({
|
|
36302
|
+
state: import_zod12.z.string().regex(/^[A-Z]{2}$/).optional().describe("Scan all active institutions in this state."),
|
|
36303
|
+
certs: import_zod12.z.array(import_zod12.z.number().int().positive()).max(50).optional().describe("Specific CERTs to scan (max 50)."),
|
|
36304
|
+
asset_min: import_zod12.z.number().positive().optional().describe("Minimum total assets ($thousands) filter."),
|
|
36305
|
+
asset_max: import_zod12.z.number().positive().optional().describe("Maximum total assets ($thousands) filter."),
|
|
36306
|
+
repdte: import_zod12.z.string().regex(/^\d{8}$/).optional().describe("Report Date (YYYYMMDD). Defaults to the most recent quarter."),
|
|
36307
|
+
min_severity: import_zod12.z.enum(["info", "warning", "critical"]).default("warning").describe("Minimum severity level to include in results (default: warning)."),
|
|
36308
|
+
quarters: import_zod12.z.number().int().min(1).max(12).default(4).describe("Prior quarters to fetch for trend analysis (default 4)."),
|
|
36309
|
+
limit: import_zod12.z.number().int().min(1).max(100).default(25).describe("Max flagged institutions to return.")
|
|
36425
36310
|
});
|
|
36426
36311
|
function registerRiskSignalTools(server) {
|
|
36427
36312
|
server.registerTool(
|
|
@@ -36444,6 +36329,7 @@ Output: Per-institution risk signals ranked by severity count. The proxy engine
|
|
|
36444
36329
|
|
|
36445
36330
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
36446
36331
|
inputSchema: RiskSignalsInputSchema,
|
|
36332
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
36447
36333
|
annotations: {
|
|
36448
36334
|
readOnlyHint: true,
|
|
36449
36335
|
destructiveHint: false,
|
|
@@ -36663,7 +36549,7 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36663
36549
|
}
|
|
36664
36550
|
|
|
36665
36551
|
// src/tools/creditConcentration.ts
|
|
36666
|
-
var
|
|
36552
|
+
var import_zod13 = require("zod");
|
|
36667
36553
|
|
|
36668
36554
|
// src/tools/shared/creditConcentration.ts
|
|
36669
36555
|
var CREDIT_FIELDS = [
|
|
@@ -36779,9 +36665,9 @@ function formatCreditSummaryText(summary) {
|
|
|
36779
36665
|
}
|
|
36780
36666
|
return parts.join("\n");
|
|
36781
36667
|
}
|
|
36782
|
-
var CreditConcentrationSchema =
|
|
36783
|
-
cert:
|
|
36784
|
-
repdte:
|
|
36668
|
+
var CreditConcentrationSchema = import_zod13.z.object({
|
|
36669
|
+
cert: import_zod13.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
36670
|
+
repdte: import_zod13.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
36785
36671
|
});
|
|
36786
36672
|
function registerCreditConcentrationTools(server) {
|
|
36787
36673
|
server.registerTool(
|
|
@@ -36799,6 +36685,7 @@ Output includes:
|
|
|
36799
36685
|
|
|
36800
36686
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
36801
36687
|
inputSchema: CreditConcentrationSchema,
|
|
36688
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
36802
36689
|
annotations: {
|
|
36803
36690
|
readOnlyHint: true,
|
|
36804
36691
|
destructiveHint: false,
|
|
@@ -36885,7 +36772,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
36885
36772
|
}
|
|
36886
36773
|
|
|
36887
36774
|
// src/tools/fundingProfile.ts
|
|
36888
|
-
var
|
|
36775
|
+
var import_zod14 = require("zod");
|
|
36889
36776
|
|
|
36890
36777
|
// src/tools/shared/fundingProfile.ts
|
|
36891
36778
|
var FUNDING_FIELDS = "CERT,REPDTE,ASSET,DEP,DEPDOM,DEPFOR,COREDEP,BROR,FREPP,EFREPP,EINTEXP,DEPDASTR,CHBALR,LNLSDEPR";
|
|
@@ -36985,9 +36872,9 @@ function formatFundingSummaryText(summary) {
|
|
|
36985
36872
|
}
|
|
36986
36873
|
return parts.join("\n");
|
|
36987
36874
|
}
|
|
36988
|
-
var FundingProfileSchema =
|
|
36989
|
-
cert:
|
|
36990
|
-
repdte:
|
|
36875
|
+
var FundingProfileSchema = import_zod14.z.object({
|
|
36876
|
+
cert: import_zod14.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
36877
|
+
repdte: import_zod14.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
36991
36878
|
});
|
|
36992
36879
|
function registerFundingProfileTools(server) {
|
|
36993
36880
|
server.registerTool(
|
|
@@ -37005,6 +36892,7 @@ Output includes:
|
|
|
37005
36892
|
|
|
37006
36893
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
37007
36894
|
inputSchema: FundingProfileSchema,
|
|
36895
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37008
36896
|
annotations: {
|
|
37009
36897
|
readOnlyHint: true,
|
|
37010
36898
|
destructiveHint: false,
|
|
@@ -37091,7 +36979,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
37091
36979
|
}
|
|
37092
36980
|
|
|
37093
36981
|
// src/tools/securitiesPortfolio.ts
|
|
37094
|
-
var
|
|
36982
|
+
var import_zod15 = require("zod");
|
|
37095
36983
|
|
|
37096
36984
|
// src/tools/shared/securitiesPortfolio.ts
|
|
37097
36985
|
var SECURITIES_FIELDS = "CERT,REPDTE,ASSET,EQTOT,SC,SCRES";
|
|
@@ -37176,9 +37064,9 @@ function formatSecuritiesSummaryText(summary) {
|
|
|
37176
37064
|
}
|
|
37177
37065
|
return parts.join("\n");
|
|
37178
37066
|
}
|
|
37179
|
-
var SecuritiesPortfolioSchema =
|
|
37180
|
-
cert:
|
|
37181
|
-
repdte:
|
|
37067
|
+
var SecuritiesPortfolioSchema = import_zod15.z.object({
|
|
37068
|
+
cert: import_zod15.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37069
|
+
repdte: import_zod15.z.string().regex(/^\d{8}$/).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
37182
37070
|
});
|
|
37183
37071
|
function registerSecuritiesPortfolioTools(server) {
|
|
37184
37072
|
server.registerTool(
|
|
@@ -37196,6 +37084,7 @@ Output includes:
|
|
|
37196
37084
|
|
|
37197
37085
|
NOTE: This is an analytical tool based on public financial data. AFS/HTM breakdown is not currently available from the FDIC API.`,
|
|
37198
37086
|
inputSchema: SecuritiesPortfolioSchema,
|
|
37087
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37199
37088
|
annotations: {
|
|
37200
37089
|
readOnlyHint: true,
|
|
37201
37090
|
destructiveHint: false,
|
|
@@ -37282,7 +37171,7 @@ NOTE: This is an analytical tool based on public financial data. AFS/HTM breakdo
|
|
|
37282
37171
|
}
|
|
37283
37172
|
|
|
37284
37173
|
// src/tools/ubprAnalysis.ts
|
|
37285
|
-
var
|
|
37174
|
+
var import_zod16 = require("zod");
|
|
37286
37175
|
|
|
37287
37176
|
// src/tools/shared/ubprRatios.ts
|
|
37288
37177
|
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";
|
|
@@ -37393,9 +37282,9 @@ function formatUbprSummaryText(summary) {
|
|
|
37393
37282
|
parts.push("UBPR-equivalent calculations, not official FFIEC UBPR output.");
|
|
37394
37283
|
return parts.join("\n");
|
|
37395
37284
|
}
|
|
37396
|
-
var UbprAnalysisSchema =
|
|
37397
|
-
cert:
|
|
37398
|
-
repdte:
|
|
37285
|
+
var UbprAnalysisSchema = import_zod16.z.object({
|
|
37286
|
+
cert: import_zod16.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37287
|
+
repdte: import_zod16.z.string().length(8).optional().describe("Report date (YYYYMMDD). Defaults to most recent quarter.")
|
|
37399
37288
|
});
|
|
37400
37289
|
function registerUbprAnalysisTools(server) {
|
|
37401
37290
|
server.registerTool(
|
|
@@ -37414,6 +37303,7 @@ Output includes:
|
|
|
37414
37303
|
|
|
37415
37304
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
37416
37305
|
inputSchema: UbprAnalysisSchema,
|
|
37306
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37417
37307
|
annotations: {
|
|
37418
37308
|
readOnlyHint: true,
|
|
37419
37309
|
destructiveHint: false,
|
|
@@ -37514,7 +37404,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
37514
37404
|
}
|
|
37515
37405
|
|
|
37516
37406
|
// src/tools/marketShareAnalysis.ts
|
|
37517
|
-
var
|
|
37407
|
+
var import_zod17 = require("zod");
|
|
37518
37408
|
|
|
37519
37409
|
// src/tools/shared/marketShare.ts
|
|
37520
37410
|
function computeMarketShare(branches) {
|
|
@@ -37627,16 +37517,16 @@ function formatMarketShareText(summary) {
|
|
|
37627
37517
|
function getDefaultSodYear() {
|
|
37628
37518
|
return (/* @__PURE__ */ new Date()).getFullYear() - 1;
|
|
37629
37519
|
}
|
|
37630
|
-
var MarketShareInputSchema =
|
|
37631
|
-
msa:
|
|
37520
|
+
var MarketShareInputSchema = import_zod17.z.object({
|
|
37521
|
+
msa: import_zod17.z.number().int().positive().optional().describe(
|
|
37632
37522
|
"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."
|
|
37633
37523
|
),
|
|
37634
|
-
city:
|
|
37635
|
-
state:
|
|
37524
|
+
city: import_zod17.z.string().optional().describe('City name (e.g., "Austin"). Requires state.'),
|
|
37525
|
+
state: import_zod17.z.string().length(2).optional().describe(
|
|
37636
37526
|
"Two-letter state abbreviation (e.g., TX). Required when using city filter."
|
|
37637
37527
|
),
|
|
37638
|
-
year:
|
|
37639
|
-
cert:
|
|
37528
|
+
year: import_zod17.z.number().int().min(1994).optional().describe("SOD report year (1994-present). Defaults to most recent."),
|
|
37529
|
+
cert: import_zod17.z.number().int().positive().optional().describe("Highlight a specific institution in the results.")
|
|
37640
37530
|
});
|
|
37641
37531
|
function registerMarketShareAnalysisTools(server) {
|
|
37642
37532
|
server.registerTool(
|
|
@@ -37659,6 +37549,7 @@ Output includes:
|
|
|
37659
37549
|
|
|
37660
37550
|
Requires at least one of: msa (numeric MSABR code), or city + state.`,
|
|
37661
37551
|
inputSchema: MarketShareInputSchema,
|
|
37552
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37662
37553
|
annotations: {
|
|
37663
37554
|
readOnlyHint: true,
|
|
37664
37555
|
destructiveHint: false,
|
|
@@ -37774,7 +37665,7 @@ Requires at least one of: msa (numeric MSABR code), or city + state.`,
|
|
|
37774
37665
|
}
|
|
37775
37666
|
|
|
37776
37667
|
// src/tools/franchiseFootprint.ts
|
|
37777
|
-
var
|
|
37668
|
+
var import_zod18 = require("zod");
|
|
37778
37669
|
var SOD_BRANCH_FIELDS = "CERT,NAMEFULL,DEPSUMBR,BRNUM,MSABR,STALPBR,YEAR";
|
|
37779
37670
|
var SOD_FETCH_LIMIT2 = 1e4;
|
|
37780
37671
|
var NON_MSA_LABEL = "Non-MSA / Rural";
|
|
@@ -37813,9 +37704,9 @@ function formatFranchiseFootprintText(summary) {
|
|
|
37813
37704
|
function getDefaultSodYear2() {
|
|
37814
37705
|
return (/* @__PURE__ */ new Date()).getFullYear() - 1;
|
|
37815
37706
|
}
|
|
37816
|
-
var FranchiseFootprintInputSchema =
|
|
37817
|
-
cert:
|
|
37818
|
-
year:
|
|
37707
|
+
var FranchiseFootprintInputSchema = import_zod18.z.object({
|
|
37708
|
+
cert: import_zod18.z.number().int().positive().describe("FDIC Certificate Number"),
|
|
37709
|
+
year: import_zod18.z.number().int().min(1994).optional().describe("SOD report year. Defaults to most recent.")
|
|
37819
37710
|
});
|
|
37820
37711
|
function registerFranchiseFootprintTools(server) {
|
|
37821
37712
|
server.registerTool(
|
|
@@ -37833,6 +37724,7 @@ Output includes:
|
|
|
37833
37724
|
|
|
37834
37725
|
Branches outside MSAs are grouped under "Non-MSA / Rural".`,
|
|
37835
37726
|
inputSchema: FranchiseFootprintInputSchema,
|
|
37727
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
37836
37728
|
annotations: {
|
|
37837
37729
|
readOnlyHint: true,
|
|
37838
37730
|
destructiveHint: false,
|
|
@@ -37953,7 +37845,7 @@ Branches outside MSAs are grouped under "Non-MSA / Rural".`,
|
|
|
37953
37845
|
}
|
|
37954
37846
|
|
|
37955
37847
|
// src/tools/holdingCompanyProfile.ts
|
|
37956
|
-
var
|
|
37848
|
+
var import_zod19 = require("zod");
|
|
37957
37849
|
|
|
37958
37850
|
// src/tools/shared/holdingCompany.ts
|
|
37959
37851
|
var INDEPENDENT_LABEL = "(Independent)";
|
|
@@ -38070,11 +37962,11 @@ function formatHoldingCompanyProfileText(result) {
|
|
|
38070
37962
|
}
|
|
38071
37963
|
return parts.join("\n");
|
|
38072
37964
|
}
|
|
38073
|
-
var HoldingCompanyProfileSchema =
|
|
38074
|
-
hc_name:
|
|
37965
|
+
var HoldingCompanyProfileSchema = import_zod19.z.object({
|
|
37966
|
+
hc_name: import_zod19.z.string().optional().describe(
|
|
38075
37967
|
'Holding company name (e.g., "JPMORGAN CHASE & CO"). Uses NAMEHCR field.'
|
|
38076
37968
|
),
|
|
38077
|
-
cert:
|
|
37969
|
+
cert: import_zod19.z.number().int().positive().optional().describe(
|
|
38078
37970
|
"CERT of any subsidiary \u2014 looks up its holding company, then profiles the entire HC."
|
|
38079
37971
|
)
|
|
38080
37972
|
});
|
|
@@ -38092,6 +37984,7 @@ Output includes:
|
|
|
38092
37984
|
|
|
38093
37985
|
NOTE: This is an analytical tool based on public financial data.`,
|
|
38094
37986
|
inputSchema: HoldingCompanyProfileSchema,
|
|
37987
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
38095
37988
|
annotations: {
|
|
38096
37989
|
readOnlyHint: true,
|
|
38097
37990
|
destructiveHint: false,
|
|
@@ -38262,7 +38155,7 @@ NOTE: This is an analytical tool based on public financial data.`,
|
|
|
38262
38155
|
}
|
|
38263
38156
|
|
|
38264
38157
|
// src/tools/regionalContext.ts
|
|
38265
|
-
var
|
|
38158
|
+
var import_zod20 = require("zod");
|
|
38266
38159
|
|
|
38267
38160
|
// src/services/fredClient.ts
|
|
38268
38161
|
var import_axios2 = __toESM(require("axios"));
|
|
@@ -38462,14 +38355,14 @@ function formatRegionalContextText(summary) {
|
|
|
38462
38355
|
parts.push("Note: Economic data from FRED (Federal Reserve Economic Data).");
|
|
38463
38356
|
return parts.join("\n");
|
|
38464
38357
|
}
|
|
38465
|
-
var RegionalContextSchema =
|
|
38466
|
-
cert:
|
|
38358
|
+
var RegionalContextSchema = import_zod20.z.object({
|
|
38359
|
+
cert: import_zod20.z.number().int().positive().optional().describe(
|
|
38467
38360
|
"FDIC Certificate Number \u2014 auto-detects state from institution record."
|
|
38468
38361
|
),
|
|
38469
|
-
state:
|
|
38362
|
+
state: import_zod20.z.string().length(2).optional().describe(
|
|
38470
38363
|
"Two-letter state abbreviation (e.g., TX). Alternative to cert-based lookup."
|
|
38471
38364
|
),
|
|
38472
|
-
repdte:
|
|
38365
|
+
repdte: import_zod20.z.string().regex(/^\d{8}$/).optional().describe(
|
|
38473
38366
|
"Reference report date (YYYYMMDD). FRED data fetched for 2 years before this date."
|
|
38474
38367
|
)
|
|
38475
38368
|
});
|
|
@@ -38488,6 +38381,7 @@ Output includes:
|
|
|
38488
38381
|
|
|
38489
38382
|
NOTE: Requires FRED_API_KEY environment variable for reliable data access. Degrades gracefully without it.`,
|
|
38490
38383
|
inputSchema: RegionalContextSchema,
|
|
38384
|
+
outputSchema: FdicAnalysisOutputSchema,
|
|
38491
38385
|
annotations: {
|
|
38492
38386
|
readOnlyHint: true,
|
|
38493
38387
|
destructiveHint: false,
|
|
@@ -38625,6 +38519,909 @@ NOTE: Requires FRED_API_KEY environment variable for reliable data access. Degra
|
|
|
38625
38519
|
);
|
|
38626
38520
|
}
|
|
38627
38521
|
|
|
38522
|
+
// src/tools/chatgptRetrieval.ts
|
|
38523
|
+
var import_zod21 = require("zod");
|
|
38524
|
+
|
|
38525
|
+
// src/tools/shared/chatgptUrls.ts
|
|
38526
|
+
var FDIC_BANKFIND_BASE_URL = "https://banks.data.fdic.gov/bankfind-suite";
|
|
38527
|
+
var FDIC_FAILED_BANK_LIST_URL = "https://www.fdic.gov/bank-failures/failed-bank-list";
|
|
38528
|
+
var PROJECT_TOOL_REFERENCE_URL = "https://jflamb.github.io/fdic-mcp-server/tool-reference/";
|
|
38529
|
+
function getInstitutionUrl(cert) {
|
|
38530
|
+
return `${FDIC_BANKFIND_BASE_URL}/bankfind/details/${cert}`;
|
|
38531
|
+
}
|
|
38532
|
+
function getFailedBankListUrl() {
|
|
38533
|
+
return FDIC_FAILED_BANK_LIST_URL;
|
|
38534
|
+
}
|
|
38535
|
+
function getSchemaDocsUrl(endpoint) {
|
|
38536
|
+
return `${PROJECT_TOOL_REFERENCE_URL}#${encodeURIComponent(endpoint)}`;
|
|
38537
|
+
}
|
|
38538
|
+
function getBranchCitationUrl() {
|
|
38539
|
+
return `${PROJECT_TOOL_REFERENCE_URL}#fdic_search_locations`;
|
|
38540
|
+
}
|
|
38541
|
+
|
|
38542
|
+
// src/tools/chatgptRetrieval.ts
|
|
38543
|
+
var SearchInputSchema = import_zod21.z.object({
|
|
38544
|
+
query: import_zod21.z.string().min(1).describe("Natural-language search query.")
|
|
38545
|
+
});
|
|
38546
|
+
var FetchInputSchema = import_zod21.z.object({
|
|
38547
|
+
id: import_zod21.z.string().min(1).describe(
|
|
38548
|
+
"Retrieval item id, such as institution:<CERT>, failure:<CERT>, branch:<UNINUM>, or schema:<endpoint>."
|
|
38549
|
+
)
|
|
38550
|
+
});
|
|
38551
|
+
var MAX_SEARCH_RESULTS = 8;
|
|
38552
|
+
function asString(value) {
|
|
38553
|
+
if (value === void 0 || value === null) {
|
|
38554
|
+
return "";
|
|
38555
|
+
}
|
|
38556
|
+
return String(value);
|
|
38557
|
+
}
|
|
38558
|
+
function asNumber2(value) {
|
|
38559
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
38560
|
+
return value;
|
|
38561
|
+
}
|
|
38562
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
38563
|
+
const parsed = Number(value);
|
|
38564
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
38565
|
+
}
|
|
38566
|
+
return void 0;
|
|
38567
|
+
}
|
|
38568
|
+
function jsonText(payload) {
|
|
38569
|
+
return JSON.stringify(payload);
|
|
38570
|
+
}
|
|
38571
|
+
function escapeFilterValue(value) {
|
|
38572
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').trim();
|
|
38573
|
+
}
|
|
38574
|
+
function normalizeQuery(query) {
|
|
38575
|
+
return query.replace(/\s+/g, " ").trim();
|
|
38576
|
+
}
|
|
38577
|
+
function extractCertLikeNumber(query) {
|
|
38578
|
+
const match = query.match(/\b(?:cert(?:ificate)?\s*#?\s*)?(\d{1,7})\b/i);
|
|
38579
|
+
if (!match) {
|
|
38580
|
+
return void 0;
|
|
38581
|
+
}
|
|
38582
|
+
return Number.parseInt(match[1], 10);
|
|
38583
|
+
}
|
|
38584
|
+
function shouldSearchFailures(query) {
|
|
38585
|
+
return /\b(fail|failed|failure|closed|resolution|receivership)\b/i.test(
|
|
38586
|
+
query
|
|
38587
|
+
);
|
|
38588
|
+
}
|
|
38589
|
+
function shouldSearchBranches(query) {
|
|
38590
|
+
return /\b(branch|branches|office|offices|location|locations|address|city|county|zip|market|msa)\b/i.test(
|
|
38591
|
+
query
|
|
38592
|
+
);
|
|
38593
|
+
}
|
|
38594
|
+
function shouldSearchSchemas(query) {
|
|
38595
|
+
return /\b(schema|field|fields|column|columns|endpoint|call report|financials?|sod|summary of deposits|demographics|metadata)\b/i.test(
|
|
38596
|
+
query
|
|
38597
|
+
);
|
|
38598
|
+
}
|
|
38599
|
+
function getRecordTitle(record, fallback) {
|
|
38600
|
+
return asString(record.NAME) || asString(record.UNINAME) || asString(record.NAMEFULL) || asString(record.OFFNAME) || fallback;
|
|
38601
|
+
}
|
|
38602
|
+
function formatLocation(record) {
|
|
38603
|
+
return [record.CITY, record.STALP].map(asString).filter(Boolean).join(", ");
|
|
38604
|
+
}
|
|
38605
|
+
function buildBranchId(record) {
|
|
38606
|
+
const uninum = asString(record.UNINUM);
|
|
38607
|
+
if (uninum) {
|
|
38608
|
+
return `branch:${encodeURIComponent(uninum)}`;
|
|
38609
|
+
}
|
|
38610
|
+
const cert = asString(record.CERT);
|
|
38611
|
+
const brnum = asString(record.BRNUM);
|
|
38612
|
+
const zip = asString(record.ZIP);
|
|
38613
|
+
if (!cert || !brnum) {
|
|
38614
|
+
return void 0;
|
|
38615
|
+
}
|
|
38616
|
+
return `branch:${encodeURIComponent([cert, brnum, zip].join("~"))}`;
|
|
38617
|
+
}
|
|
38618
|
+
function institutionSearchResult(record) {
|
|
38619
|
+
const cert = asNumber2(record.CERT);
|
|
38620
|
+
if (cert === void 0) {
|
|
38621
|
+
return void 0;
|
|
38622
|
+
}
|
|
38623
|
+
const title = getRecordTitle(record, `FDIC institution ${cert}`);
|
|
38624
|
+
const location = formatLocation(record);
|
|
38625
|
+
return {
|
|
38626
|
+
id: `institution:${cert}`,
|
|
38627
|
+
title: location ? `${title} (${location})` : title,
|
|
38628
|
+
url: getInstitutionUrl(cert)
|
|
38629
|
+
};
|
|
38630
|
+
}
|
|
38631
|
+
function failureSearchResult(record) {
|
|
38632
|
+
const cert = asNumber2(record.CERT);
|
|
38633
|
+
if (cert === void 0) {
|
|
38634
|
+
return void 0;
|
|
38635
|
+
}
|
|
38636
|
+
const title = getRecordTitle(record, `Failed bank ${cert}`);
|
|
38637
|
+
const failDate = asString(record.FAILDATE);
|
|
38638
|
+
return {
|
|
38639
|
+
id: `failure:${cert}`,
|
|
38640
|
+
title: failDate ? `${title} failed ${failDate}` : `${title} failure record`,
|
|
38641
|
+
url: getFailedBankListUrl()
|
|
38642
|
+
};
|
|
38643
|
+
}
|
|
38644
|
+
function branchSearchResult(record) {
|
|
38645
|
+
const id = buildBranchId(record);
|
|
38646
|
+
if (!id) {
|
|
38647
|
+
return void 0;
|
|
38648
|
+
}
|
|
38649
|
+
const name = getRecordTitle(record, "FDIC branch location");
|
|
38650
|
+
const address = [record.ADDRESS, record.CITY, record.STALP, record.ZIP].map(asString).filter(Boolean).join(", ");
|
|
38651
|
+
return {
|
|
38652
|
+
id,
|
|
38653
|
+
title: address ? `${name} - ${address}` : name,
|
|
38654
|
+
url: getBranchCitationUrl()
|
|
38655
|
+
};
|
|
38656
|
+
}
|
|
38657
|
+
function schemaSearchResults(query) {
|
|
38658
|
+
const normalized = normalizeQuery(query).toLowerCase();
|
|
38659
|
+
const metadata = listEndpointMetadata();
|
|
38660
|
+
return metadata.filter((endpoint) => {
|
|
38661
|
+
if (normalized.includes(endpoint.endpoint.toLowerCase())) {
|
|
38662
|
+
return true;
|
|
38663
|
+
}
|
|
38664
|
+
if (endpoint.title.toLowerCase().includes(normalized)) {
|
|
38665
|
+
return true;
|
|
38666
|
+
}
|
|
38667
|
+
return Object.keys(endpoint.fields).some(
|
|
38668
|
+
(field) => normalized.includes(field.toLowerCase())
|
|
38669
|
+
);
|
|
38670
|
+
}).slice(0, 2).map((endpoint) => ({
|
|
38671
|
+
id: `schema:${endpoint.endpoint}`,
|
|
38672
|
+
title: `${endpoint.title} schema`,
|
|
38673
|
+
url: getSchemaDocsUrl(endpoint.endpoint)
|
|
38674
|
+
}));
|
|
38675
|
+
}
|
|
38676
|
+
async function searchInstitutions(query) {
|
|
38677
|
+
const cert = extractCertLikeNumber(query);
|
|
38678
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : `NAME:"${escapeFilterValue(query)}"`;
|
|
38679
|
+
const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
38680
|
+
filters,
|
|
38681
|
+
fields: "CERT,NAME,CITY,STALP,ACTIVE",
|
|
38682
|
+
limit: 3,
|
|
38683
|
+
sort_by: "ACTIVE",
|
|
38684
|
+
sort_order: "DESC"
|
|
38685
|
+
});
|
|
38686
|
+
return extractRecords(response).map(institutionSearchResult).filter((result) => result !== void 0);
|
|
38687
|
+
}
|
|
38688
|
+
async function searchFailures(query) {
|
|
38689
|
+
const cert = extractCertLikeNumber(query);
|
|
38690
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : `NAME:"${escapeFilterValue(query)}"`;
|
|
38691
|
+
const response = await queryEndpoint(ENDPOINTS.FAILURES, {
|
|
38692
|
+
filters,
|
|
38693
|
+
fields: "CERT,NAME,CITY,STALP,FAILDATE,RESTYPE",
|
|
38694
|
+
limit: 2,
|
|
38695
|
+
sort_by: "FAILDATE",
|
|
38696
|
+
sort_order: "DESC"
|
|
38697
|
+
});
|
|
38698
|
+
return extractRecords(response).map(failureSearchResult).filter((result) => result !== void 0);
|
|
38699
|
+
}
|
|
38700
|
+
async function searchBranches(query) {
|
|
38701
|
+
const cert = extractCertLikeNumber(query);
|
|
38702
|
+
const normalized = normalizeQuery(query);
|
|
38703
|
+
const zip = normalized.match(/\b\d{5}\b/)?.[0];
|
|
38704
|
+
const filters = cert !== void 0 ? `CERT:${cert}` : zip !== void 0 ? `ZIP:${zip}` : `CITY:"${escapeFilterValue(normalized)}"`;
|
|
38705
|
+
const response = await queryEndpoint(ENDPOINTS.LOCATIONS, {
|
|
38706
|
+
filters,
|
|
38707
|
+
fields: "UNINUM,CERT,NAME,OFFNAME,ADDRESS,CITY,STALP,ZIP",
|
|
38708
|
+
limit: 3,
|
|
38709
|
+
sort_by: "UNINUM",
|
|
38710
|
+
sort_order: "ASC"
|
|
38711
|
+
});
|
|
38712
|
+
return extractRecords(response).map(branchSearchResult).filter((result) => result !== void 0);
|
|
38713
|
+
}
|
|
38714
|
+
async function safeSearch(searcher) {
|
|
38715
|
+
try {
|
|
38716
|
+
return await searcher();
|
|
38717
|
+
} catch {
|
|
38718
|
+
return [];
|
|
38719
|
+
}
|
|
38720
|
+
}
|
|
38721
|
+
function dedupeResults(results) {
|
|
38722
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38723
|
+
const deduped = [];
|
|
38724
|
+
for (const result of results) {
|
|
38725
|
+
if (seen.has(result.id)) {
|
|
38726
|
+
continue;
|
|
38727
|
+
}
|
|
38728
|
+
seen.add(result.id);
|
|
38729
|
+
deduped.push(result);
|
|
38730
|
+
}
|
|
38731
|
+
return deduped.slice(0, MAX_SEARCH_RESULTS);
|
|
38732
|
+
}
|
|
38733
|
+
function recordText(record, fields) {
|
|
38734
|
+
return fields.map((field) => [field, asString(record[field])]).filter(([, value]) => value !== "").map(([field, value]) => `${field}: ${value}`).join("\n");
|
|
38735
|
+
}
|
|
38736
|
+
async function fetchInstitution(cert) {
|
|
38737
|
+
const response = await queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
38738
|
+
filters: `CERT:${cert}`,
|
|
38739
|
+
limit: 1
|
|
38740
|
+
});
|
|
38741
|
+
const record = extractRecords(response)[0];
|
|
38742
|
+
if (!record) {
|
|
38743
|
+
throw new Error(`No institution found for CERT ${cert}.`);
|
|
38744
|
+
}
|
|
38745
|
+
const title = getRecordTitle(record, `FDIC institution ${cert}`);
|
|
38746
|
+
return {
|
|
38747
|
+
id: `institution:${cert}`,
|
|
38748
|
+
title,
|
|
38749
|
+
text: recordText(record, [
|
|
38750
|
+
"CERT",
|
|
38751
|
+
"NAME",
|
|
38752
|
+
"CITY",
|
|
38753
|
+
"STALP",
|
|
38754
|
+
"STNAME",
|
|
38755
|
+
"ACTIVE",
|
|
38756
|
+
"ASSET",
|
|
38757
|
+
"DEP",
|
|
38758
|
+
"OFFICES",
|
|
38759
|
+
"BKCLASS",
|
|
38760
|
+
"REGAGNT",
|
|
38761
|
+
"ESTYMD"
|
|
38762
|
+
]),
|
|
38763
|
+
url: getInstitutionUrl(cert),
|
|
38764
|
+
metadata: { type: "institution", cert, source: "FDIC BankFind Suite" }
|
|
38765
|
+
};
|
|
38766
|
+
}
|
|
38767
|
+
async function fetchFailure(cert) {
|
|
38768
|
+
const response = await queryEndpoint(ENDPOINTS.FAILURES, {
|
|
38769
|
+
filters: `CERT:${cert}`,
|
|
38770
|
+
limit: 1
|
|
38771
|
+
});
|
|
38772
|
+
const record = extractRecords(response)[0];
|
|
38773
|
+
if (!record) {
|
|
38774
|
+
throw new Error(`No failure record found for CERT ${cert}.`);
|
|
38775
|
+
}
|
|
38776
|
+
const title = getRecordTitle(record, `Failed bank ${cert}`);
|
|
38777
|
+
return {
|
|
38778
|
+
id: `failure:${cert}`,
|
|
38779
|
+
title,
|
|
38780
|
+
text: recordText(record, [
|
|
38781
|
+
"CERT",
|
|
38782
|
+
"NAME",
|
|
38783
|
+
"CITY",
|
|
38784
|
+
"STALP",
|
|
38785
|
+
"FAILDATE",
|
|
38786
|
+
"RESTYPE",
|
|
38787
|
+
"QBFASSET",
|
|
38788
|
+
"COST"
|
|
38789
|
+
]),
|
|
38790
|
+
url: getFailedBankListUrl(),
|
|
38791
|
+
metadata: { type: "failure", cert, source: "FDIC Failed Bank List" }
|
|
38792
|
+
};
|
|
38793
|
+
}
|
|
38794
|
+
async function fetchBranch(rawId) {
|
|
38795
|
+
const decoded = decodeURIComponent(rawId);
|
|
38796
|
+
const [cert, brnum, zip] = decoded.split("~");
|
|
38797
|
+
const filters = cert && brnum ? [`CERT:${cert}`, `BRNUM:${brnum}`, zip ? `ZIP:${zip}` : void 0].filter(Boolean).join(" AND ") : `UNINUM:${decoded}`;
|
|
38798
|
+
const response = await queryEndpoint(ENDPOINTS.LOCATIONS, {
|
|
38799
|
+
filters,
|
|
38800
|
+
limit: 1
|
|
38801
|
+
});
|
|
38802
|
+
const record = extractRecords(response)[0];
|
|
38803
|
+
if (!record) {
|
|
38804
|
+
throw new Error(`No branch/location found for id ${rawId}.`);
|
|
38805
|
+
}
|
|
38806
|
+
const title = getRecordTitle(record, `FDIC branch ${rawId}`);
|
|
38807
|
+
const id = buildBranchId(record) ?? `branch:${rawId}`;
|
|
38808
|
+
return {
|
|
38809
|
+
id,
|
|
38810
|
+
title,
|
|
38811
|
+
text: recordText(record, [
|
|
38812
|
+
"UNINUM",
|
|
38813
|
+
"CERT",
|
|
38814
|
+
"UNINAME",
|
|
38815
|
+
"NAMEFULL",
|
|
38816
|
+
"ADDRESS",
|
|
38817
|
+
"CITY",
|
|
38818
|
+
"STALP",
|
|
38819
|
+
"ZIP",
|
|
38820
|
+
"COUNTY",
|
|
38821
|
+
"BRNUM",
|
|
38822
|
+
"BRSERTYP",
|
|
38823
|
+
"ESTYMD",
|
|
38824
|
+
"ENDEFYMD"
|
|
38825
|
+
]),
|
|
38826
|
+
url: getBranchCitationUrl(),
|
|
38827
|
+
metadata: {
|
|
38828
|
+
type: "branch",
|
|
38829
|
+
cert: record.CERT,
|
|
38830
|
+
uninum: record.UNINUM,
|
|
38831
|
+
source: "FDIC BankFind Suite locations"
|
|
38832
|
+
}
|
|
38833
|
+
};
|
|
38834
|
+
}
|
|
38835
|
+
function fetchSchema(endpoint) {
|
|
38836
|
+
const metadata = getEndpointMetadata(endpoint);
|
|
38837
|
+
if (!metadata) {
|
|
38838
|
+
throw new Error(`No schema metadata found for endpoint ${endpoint}.`);
|
|
38839
|
+
}
|
|
38840
|
+
const fields = Object.values(metadata.fields).slice(0, 200).map((field) => {
|
|
38841
|
+
const title = field.title ? ` - ${field.title}` : "";
|
|
38842
|
+
return `${field.name}${title}`;
|
|
38843
|
+
}).join("\n");
|
|
38844
|
+
return {
|
|
38845
|
+
id: `schema:${endpoint}`,
|
|
38846
|
+
title: `${metadata.title} schema`,
|
|
38847
|
+
text: [
|
|
38848
|
+
metadata.description ?? metadata.title,
|
|
38849
|
+
"",
|
|
38850
|
+
`Endpoint: ${metadata.endpoint}`,
|
|
38851
|
+
`Source: ${metadata.source.docsBaseUrl}`,
|
|
38852
|
+
"",
|
|
38853
|
+
"Fields:",
|
|
38854
|
+
fields
|
|
38855
|
+
].join("\n"),
|
|
38856
|
+
url: getSchemaDocsUrl(endpoint),
|
|
38857
|
+
metadata: {
|
|
38858
|
+
type: "schema",
|
|
38859
|
+
endpoint,
|
|
38860
|
+
field_count: Object.keys(metadata.fields).length,
|
|
38861
|
+
source: metadata.source.docsBaseUrl
|
|
38862
|
+
}
|
|
38863
|
+
};
|
|
38864
|
+
}
|
|
38865
|
+
async function fetchById(id) {
|
|
38866
|
+
const [kind, rawValue] = id.split(":", 2);
|
|
38867
|
+
if (!kind || !rawValue) {
|
|
38868
|
+
throw new Error(
|
|
38869
|
+
"Invalid fetch id. Expected institution:<CERT>, failure:<CERT>, branch:<id>, or schema:<endpoint>."
|
|
38870
|
+
);
|
|
38871
|
+
}
|
|
38872
|
+
if (kind === "institution") {
|
|
38873
|
+
const cert = Number.parseInt(rawValue, 10);
|
|
38874
|
+
if (!Number.isInteger(cert) || cert <= 0) {
|
|
38875
|
+
throw new Error(`Invalid institution CERT in id ${id}.`);
|
|
38876
|
+
}
|
|
38877
|
+
return fetchInstitution(cert);
|
|
38878
|
+
}
|
|
38879
|
+
if (kind === "failure") {
|
|
38880
|
+
const cert = Number.parseInt(rawValue, 10);
|
|
38881
|
+
if (!Number.isInteger(cert) || cert <= 0) {
|
|
38882
|
+
throw new Error(`Invalid failure CERT in id ${id}.`);
|
|
38883
|
+
}
|
|
38884
|
+
return fetchFailure(cert);
|
|
38885
|
+
}
|
|
38886
|
+
if (kind === "branch") {
|
|
38887
|
+
return fetchBranch(rawValue);
|
|
38888
|
+
}
|
|
38889
|
+
if (kind === "schema") {
|
|
38890
|
+
return fetchSchema(rawValue);
|
|
38891
|
+
}
|
|
38892
|
+
throw new Error(`Unsupported fetch id kind: ${kind}.`);
|
|
38893
|
+
}
|
|
38894
|
+
async function runSearch(query) {
|
|
38895
|
+
const normalized = normalizeQuery(query);
|
|
38896
|
+
const shouldIncludeFailures = shouldSearchFailures(normalized);
|
|
38897
|
+
const shouldIncludeBranches = shouldSearchBranches(normalized);
|
|
38898
|
+
const shouldIncludeSchemas = shouldSearchSchemas(normalized);
|
|
38899
|
+
const [institutions, failures, branches] = await Promise.all([
|
|
38900
|
+
safeSearch(() => searchInstitutions(normalized)),
|
|
38901
|
+
shouldIncludeFailures ? safeSearch(() => searchFailures(normalized)) : Promise.resolve([]),
|
|
38902
|
+
shouldIncludeBranches ? safeSearch(() => searchBranches(normalized)) : Promise.resolve([])
|
|
38903
|
+
]);
|
|
38904
|
+
const fallbackFailures = failures.length === 0 && institutions.length === 0 ? await safeSearch(() => searchFailures(normalized)) : [];
|
|
38905
|
+
const fallbackBranches = branches.length === 0 && institutions.length === 0 ? await safeSearch(() => searchBranches(normalized)) : [];
|
|
38906
|
+
const schemas = shouldIncludeSchemas ? schemaSearchResults(normalized) : [];
|
|
38907
|
+
const results = dedupeResults([
|
|
38908
|
+
...institutions,
|
|
38909
|
+
...failures,
|
|
38910
|
+
...branches,
|
|
38911
|
+
...fallbackFailures,
|
|
38912
|
+
...fallbackBranches,
|
|
38913
|
+
...schemas
|
|
38914
|
+
]);
|
|
38915
|
+
return { results };
|
|
38916
|
+
}
|
|
38917
|
+
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.";
|
|
38918
|
+
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').";
|
|
38919
|
+
function registerSearchTool(server, name) {
|
|
38920
|
+
server.registerTool(
|
|
38921
|
+
name,
|
|
38922
|
+
{
|
|
38923
|
+
title: "Search FDIC BankFind",
|
|
38924
|
+
description: SEARCH_DESCRIPTION,
|
|
38925
|
+
inputSchema: SearchInputSchema,
|
|
38926
|
+
outputSchema: ChatGptSearchResultSchema,
|
|
38927
|
+
annotations: {
|
|
38928
|
+
readOnlyHint: true,
|
|
38929
|
+
destructiveHint: false,
|
|
38930
|
+
idempotentHint: true,
|
|
38931
|
+
openWorldHint: true
|
|
38932
|
+
}
|
|
38933
|
+
},
|
|
38934
|
+
async ({ query }) => {
|
|
38935
|
+
const payload = await runSearch(query);
|
|
38936
|
+
return {
|
|
38937
|
+
content: [{ type: "text", text: jsonText(payload) }],
|
|
38938
|
+
structuredContent: payload
|
|
38939
|
+
};
|
|
38940
|
+
}
|
|
38941
|
+
);
|
|
38942
|
+
}
|
|
38943
|
+
function registerFetchTool(server, name) {
|
|
38944
|
+
server.registerTool(
|
|
38945
|
+
name,
|
|
38946
|
+
{
|
|
38947
|
+
title: "Fetch FDIC BankFind Result",
|
|
38948
|
+
description: FETCH_DESCRIPTION,
|
|
38949
|
+
inputSchema: FetchInputSchema,
|
|
38950
|
+
outputSchema: ChatGptFetchResultSchema,
|
|
38951
|
+
annotations: {
|
|
38952
|
+
readOnlyHint: true,
|
|
38953
|
+
destructiveHint: false,
|
|
38954
|
+
idempotentHint: true,
|
|
38955
|
+
openWorldHint: true
|
|
38956
|
+
}
|
|
38957
|
+
},
|
|
38958
|
+
async ({ id }) => {
|
|
38959
|
+
try {
|
|
38960
|
+
const result = await fetchById(id);
|
|
38961
|
+
return {
|
|
38962
|
+
content: [{ type: "text", text: jsonText(result) }],
|
|
38963
|
+
structuredContent: result
|
|
38964
|
+
};
|
|
38965
|
+
} catch (err) {
|
|
38966
|
+
return formatToolError(err);
|
|
38967
|
+
}
|
|
38968
|
+
}
|
|
38969
|
+
);
|
|
38970
|
+
}
|
|
38971
|
+
function registerChatGptRetrievalTools(server, options = {}) {
|
|
38972
|
+
const includeCanonical = options.includeCanonicalNames ?? true;
|
|
38973
|
+
const includeAliases = options.includeNamespacedAliases ?? true;
|
|
38974
|
+
if (includeCanonical) {
|
|
38975
|
+
registerSearchTool(server, "search");
|
|
38976
|
+
registerFetchTool(server, "fetch");
|
|
38977
|
+
}
|
|
38978
|
+
if (includeAliases) {
|
|
38979
|
+
registerSearchTool(server, "fdic_search");
|
|
38980
|
+
registerFetchTool(server, "fdic_fetch");
|
|
38981
|
+
}
|
|
38982
|
+
}
|
|
38983
|
+
|
|
38984
|
+
// src/tools/chatgptBankDeepDive.ts
|
|
38985
|
+
var import_zod22 = require("zod");
|
|
38986
|
+
|
|
38987
|
+
// src/resources/chatgptAppResources.ts
|
|
38988
|
+
var BANK_DEEP_DIVE_WIDGET_URI = "ui://widget/fdic-bank-deep-dive-v1.html";
|
|
38989
|
+
var MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
|
|
38990
|
+
var BANK_DEEP_DIVE_WIDGET_HTML = String.raw`
|
|
38991
|
+
<div id="root" class="fdic-app">
|
|
38992
|
+
<section class="empty">Loading bank dashboard...</section>
|
|
38993
|
+
</div>
|
|
38994
|
+
<style>
|
|
38995
|
+
:root {
|
|
38996
|
+
color-scheme: light dark;
|
|
38997
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
38998
|
+
}
|
|
38999
|
+
|
|
39000
|
+
body {
|
|
39001
|
+
margin: 0;
|
|
39002
|
+
background: Canvas;
|
|
39003
|
+
color: CanvasText;
|
|
39004
|
+
}
|
|
39005
|
+
|
|
39006
|
+
.fdic-app {
|
|
39007
|
+
box-sizing: border-box;
|
|
39008
|
+
min-height: 100%;
|
|
39009
|
+
padding: 16px;
|
|
39010
|
+
}
|
|
39011
|
+
|
|
39012
|
+
.empty,
|
|
39013
|
+
.panel {
|
|
39014
|
+
border: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
|
|
39015
|
+
border-radius: 8px;
|
|
39016
|
+
padding: 14px;
|
|
39017
|
+
}
|
|
39018
|
+
|
|
39019
|
+
.header {
|
|
39020
|
+
display: grid;
|
|
39021
|
+
gap: 6px;
|
|
39022
|
+
margin-bottom: 14px;
|
|
39023
|
+
}
|
|
39024
|
+
|
|
39025
|
+
.eyebrow {
|
|
39026
|
+
color: color-mix(in srgb, CanvasText 58%, transparent);
|
|
39027
|
+
font-size: 12px;
|
|
39028
|
+
font-weight: 650;
|
|
39029
|
+
text-transform: uppercase;
|
|
39030
|
+
}
|
|
39031
|
+
|
|
39032
|
+
h1,
|
|
39033
|
+
h2,
|
|
39034
|
+
p {
|
|
39035
|
+
margin: 0;
|
|
39036
|
+
}
|
|
39037
|
+
|
|
39038
|
+
h1 {
|
|
39039
|
+
font-size: 20px;
|
|
39040
|
+
line-height: 1.25;
|
|
39041
|
+
letter-spacing: 0;
|
|
39042
|
+
}
|
|
39043
|
+
|
|
39044
|
+
h2 {
|
|
39045
|
+
font-size: 14px;
|
|
39046
|
+
line-height: 1.35;
|
|
39047
|
+
letter-spacing: 0;
|
|
39048
|
+
}
|
|
39049
|
+
|
|
39050
|
+
.subtle {
|
|
39051
|
+
color: color-mix(in srgb, CanvasText 64%, transparent);
|
|
39052
|
+
font-size: 13px;
|
|
39053
|
+
}
|
|
39054
|
+
|
|
39055
|
+
.grid {
|
|
39056
|
+
display: grid;
|
|
39057
|
+
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
|
39058
|
+
gap: 8px;
|
|
39059
|
+
margin: 12px 0;
|
|
39060
|
+
}
|
|
39061
|
+
|
|
39062
|
+
.metric {
|
|
39063
|
+
border: 1px solid color-mix(in srgb, CanvasText 12%, transparent);
|
|
39064
|
+
border-radius: 8px;
|
|
39065
|
+
padding: 10px;
|
|
39066
|
+
}
|
|
39067
|
+
|
|
39068
|
+
.metric dt {
|
|
39069
|
+
color: color-mix(in srgb, CanvasText 62%, transparent);
|
|
39070
|
+
font-size: 12px;
|
|
39071
|
+
margin: 0 0 6px;
|
|
39072
|
+
}
|
|
39073
|
+
|
|
39074
|
+
.metric dd {
|
|
39075
|
+
font-size: 16px;
|
|
39076
|
+
font-weight: 680;
|
|
39077
|
+
margin: 0;
|
|
39078
|
+
}
|
|
39079
|
+
|
|
39080
|
+
.section {
|
|
39081
|
+
margin-top: 14px;
|
|
39082
|
+
}
|
|
39083
|
+
|
|
39084
|
+
.list {
|
|
39085
|
+
display: grid;
|
|
39086
|
+
gap: 6px;
|
|
39087
|
+
margin: 8px 0 0;
|
|
39088
|
+
padding: 0;
|
|
39089
|
+
list-style: none;
|
|
39090
|
+
}
|
|
39091
|
+
|
|
39092
|
+
.list li {
|
|
39093
|
+
border-left: 3px solid color-mix(in srgb, CanvasText 24%, transparent);
|
|
39094
|
+
padding: 4px 0 4px 8px;
|
|
39095
|
+
font-size: 13px;
|
|
39096
|
+
}
|
|
39097
|
+
|
|
39098
|
+
.actions {
|
|
39099
|
+
display: flex;
|
|
39100
|
+
flex-wrap: wrap;
|
|
39101
|
+
gap: 8px;
|
|
39102
|
+
margin-top: 14px;
|
|
39103
|
+
}
|
|
39104
|
+
|
|
39105
|
+
button {
|
|
39106
|
+
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
|
|
39107
|
+
border-radius: 8px;
|
|
39108
|
+
background: Canvas;
|
|
39109
|
+
color: CanvasText;
|
|
39110
|
+
cursor: pointer;
|
|
39111
|
+
font: inherit;
|
|
39112
|
+
font-size: 13px;
|
|
39113
|
+
padding: 8px 10px;
|
|
39114
|
+
}
|
|
39115
|
+
</style>
|
|
39116
|
+
<script type="module">
|
|
39117
|
+
const root = document.getElementById("root");
|
|
39118
|
+
|
|
39119
|
+
function formatMoney(value) {
|
|
39120
|
+
if (typeof value !== "number") return "n/a";
|
|
39121
|
+
if (Math.abs(value) >= 1000000) return "$" + (value / 1000000).toFixed(1) + "B";
|
|
39122
|
+
if (Math.abs(value) >= 1000) return "$" + (value / 1000).toFixed(1) + "M";
|
|
39123
|
+
return "$" + value.toLocaleString() + "k";
|
|
39124
|
+
}
|
|
39125
|
+
|
|
39126
|
+
function escapeHtml(value) {
|
|
39127
|
+
return String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
39128
|
+
"&": "&",
|
|
39129
|
+
"<": "<",
|
|
39130
|
+
">": ">",
|
|
39131
|
+
'"': """,
|
|
39132
|
+
"'": "'",
|
|
39133
|
+
}[char]));
|
|
39134
|
+
}
|
|
39135
|
+
|
|
39136
|
+
function render(data) {
|
|
39137
|
+
const institution = data?.institution ?? {};
|
|
39138
|
+
const assessment = data?.assessment ?? {};
|
|
39139
|
+
const metrics = data?.metrics ?? {};
|
|
39140
|
+
const signals = data?.risk_signals ?? [];
|
|
39141
|
+
const warnings = data?.warnings ?? [];
|
|
39142
|
+
const sources = data?.sources ?? [];
|
|
39143
|
+
const title = institution.name || "FDIC bank dashboard";
|
|
39144
|
+
|
|
39145
|
+
const signalItems = signals.length
|
|
39146
|
+
? signals.map((signal) => "<li>" + escapeHtml(signal) + "</li>").join("")
|
|
39147
|
+
: "<li>No risk signals returned for this dashboard.</li>";
|
|
39148
|
+
const warningSection = warnings.length
|
|
39149
|
+
? '<section class="section"><h2>Warnings</h2><ul class="list">' +
|
|
39150
|
+
warnings.map((warning) => "<li>" + escapeHtml(warning) + "</li>").join("") +
|
|
39151
|
+
"</ul></section>"
|
|
39152
|
+
: "";
|
|
39153
|
+
const sourceItems = sources
|
|
39154
|
+
.map((source) => '<li><a href="' + escapeHtml(source.url) + '" target="_blank" rel="noreferrer">' + escapeHtml(source.title) + "</a></li>")
|
|
39155
|
+
.join("");
|
|
39156
|
+
|
|
39157
|
+
root.innerHTML = [
|
|
39158
|
+
'<article class="panel">',
|
|
39159
|
+
'<header class="header">',
|
|
39160
|
+
'<p class="eyebrow">FDIC BankFind</p>',
|
|
39161
|
+
"<h1>" + escapeHtml(title) + "</h1>",
|
|
39162
|
+
'<p class="subtle">' + escapeHtml(institution.city) + ", " + escapeHtml(institution.state) + " · CERT " + escapeHtml(institution.cert) + " · " + escapeHtml(institution.active ? "Active" : "Inactive or unknown") + "</p>",
|
|
39163
|
+
'<p class="subtle">Report date: ' + escapeHtml(institution.report_date ?? "latest available") + " · Public analytical proxy, not an official CAMELS rating.</p>",
|
|
39164
|
+
"</header>",
|
|
39165
|
+
'<dl class="grid">',
|
|
39166
|
+
'<div class="metric"><dt>Assets</dt><dd>' + formatMoney(institution.asset_thousands) + "</dd></div>",
|
|
39167
|
+
'<div class="metric"><dt>Deposits</dt><dd>' + formatMoney(institution.deposit_thousands) + "</dd></div>",
|
|
39168
|
+
'<div class="metric"><dt>Offices</dt><dd>' + escapeHtml(institution.offices ?? "n/a") + "</dd></div>",
|
|
39169
|
+
'<div class="metric"><dt>Proxy band</dt><dd>' + escapeHtml(assessment.proxy_band ?? "n/a") + "</dd></div>",
|
|
39170
|
+
'<div class="metric"><dt>ROA</dt><dd>' + escapeHtml(metrics.roa ?? "n/a") + "</dd></div>",
|
|
39171
|
+
'<div class="metric"><dt>Tier 1 leverage</dt><dd>' + escapeHtml(metrics.tier1_leverage ?? "n/a") + "</dd></div>",
|
|
39172
|
+
"</dl>",
|
|
39173
|
+
'<section class="section"><h2>Risk Signals</h2><ul class="list">' + signalItems + "</ul></section>",
|
|
39174
|
+
warningSection,
|
|
39175
|
+
'<section class="section"><h2>Sources</h2><ul class="list">' + sourceItems + "</ul></section>",
|
|
39176
|
+
'<div class="actions">',
|
|
39177
|
+
'<button type="button" data-message="Compare CERT ' + escapeHtml(institution.cert) + ' with peers.">Compare peers</button>',
|
|
39178
|
+
'<button type="button" data-message="Show the branch footprint for CERT ' + escapeHtml(institution.cert) + '.">Branch footprint</button>',
|
|
39179
|
+
'<button type="button" data-message="Analyze the funding profile for CERT ' + escapeHtml(institution.cert) + '.">Funding profile</button>',
|
|
39180
|
+
"</div>",
|
|
39181
|
+
"</article>",
|
|
39182
|
+
].join("");
|
|
39183
|
+
}
|
|
39184
|
+
|
|
39185
|
+
window.addEventListener("message", (event) => {
|
|
39186
|
+
const message = event.data;
|
|
39187
|
+
if (message?.method === "ui/notifications/tool-result") {
|
|
39188
|
+
render(message.params?.structuredContent);
|
|
39189
|
+
}
|
|
39190
|
+
});
|
|
39191
|
+
|
|
39192
|
+
root.addEventListener("click", (event) => {
|
|
39193
|
+
const button = event.target.closest("button[data-message]");
|
|
39194
|
+
if (!button) return;
|
|
39195
|
+
const text = button.getAttribute("data-message");
|
|
39196
|
+
window.parent.postMessage({
|
|
39197
|
+
jsonrpc: "2.0",
|
|
39198
|
+
method: "ui/message",
|
|
39199
|
+
params: { role: "user", content: [{ type: "text", text }] },
|
|
39200
|
+
}, "*");
|
|
39201
|
+
});
|
|
39202
|
+
|
|
39203
|
+
const initial = window.openai?.toolOutput ?? window.openai?.toolResponse?.structuredContent;
|
|
39204
|
+
if (initial) {
|
|
39205
|
+
render(initial);
|
|
39206
|
+
}
|
|
39207
|
+
</script>
|
|
39208
|
+
`.trim();
|
|
39209
|
+
function registerChatGptAppResources(server) {
|
|
39210
|
+
server.registerResource(
|
|
39211
|
+
"fdic-bank-deep-dive-widget",
|
|
39212
|
+
BANK_DEEP_DIVE_WIDGET_URI,
|
|
39213
|
+
{
|
|
39214
|
+
title: "FDIC Bank Deep Dive Widget",
|
|
39215
|
+
description: "Interactive ChatGPT widget for a public FDIC bank deep-dive dashboard.",
|
|
39216
|
+
mimeType: MCP_APP_MIME_TYPE
|
|
39217
|
+
},
|
|
39218
|
+
async () => ({
|
|
39219
|
+
contents: [
|
|
39220
|
+
{
|
|
39221
|
+
uri: BANK_DEEP_DIVE_WIDGET_URI,
|
|
39222
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
39223
|
+
text: BANK_DEEP_DIVE_WIDGET_HTML,
|
|
39224
|
+
_meta: {
|
|
39225
|
+
ui: {
|
|
39226
|
+
prefersBorder: true,
|
|
39227
|
+
csp: {
|
|
39228
|
+
connectDomains: [],
|
|
39229
|
+
resourceDomains: []
|
|
39230
|
+
}
|
|
39231
|
+
},
|
|
39232
|
+
"openai/widgetDescription": "Renders an FDIC bank deep-dive dashboard from public BankFind data.",
|
|
39233
|
+
"openai/widgetPrefersBorder": true,
|
|
39234
|
+
"openai/widgetCSP": {
|
|
39235
|
+
connect_domains: [],
|
|
39236
|
+
resource_domains: []
|
|
39237
|
+
}
|
|
39238
|
+
}
|
|
39239
|
+
}
|
|
39240
|
+
]
|
|
39241
|
+
})
|
|
39242
|
+
);
|
|
39243
|
+
}
|
|
39244
|
+
|
|
39245
|
+
// src/tools/chatgptBankDeepDive.ts
|
|
39246
|
+
var BankDeepDiveInputSchema = import_zod22.z.object({
|
|
39247
|
+
cert: import_zod22.z.number().int().positive().describe("FDIC Certificate Number of the institution to render."),
|
|
39248
|
+
repdte: import_zod22.z.string().regex(/^\d{8}$/).optional().describe(
|
|
39249
|
+
"Quarter-end report date in YYYYMMDD format. Defaults to the most recent likely published quarter."
|
|
39250
|
+
)
|
|
39251
|
+
});
|
|
39252
|
+
function asNumber3(value) {
|
|
39253
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
39254
|
+
}
|
|
39255
|
+
function asString2(value) {
|
|
39256
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
39257
|
+
}
|
|
39258
|
+
function formatRatio(value) {
|
|
39259
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(2)}%` : void 0;
|
|
39260
|
+
}
|
|
39261
|
+
function collectDashboardRiskSignals(financials) {
|
|
39262
|
+
if (!financials) {
|
|
39263
|
+
return [];
|
|
39264
|
+
}
|
|
39265
|
+
const signals = [];
|
|
39266
|
+
const tier1Leverage = asNumber3(financials.IDT1CER);
|
|
39267
|
+
const roa = asNumber3(financials.ROA);
|
|
39268
|
+
const noncurrentLoans = asNumber3(financials.NCLNLSR);
|
|
39269
|
+
const brokeredDeposits = asNumber3(financials.BRO);
|
|
39270
|
+
if (tier1Leverage !== void 0 && tier1Leverage < 5) {
|
|
39271
|
+
signals.push(
|
|
39272
|
+
`Tier 1 leverage ratio is ${tier1Leverage.toFixed(2)}%, below the 5% well-capitalized threshold.`
|
|
39273
|
+
);
|
|
39274
|
+
}
|
|
39275
|
+
if (roa !== void 0 && roa < 0) {
|
|
39276
|
+
signals.push(`Return on assets is negative at ${roa.toFixed(2)}%.`);
|
|
39277
|
+
}
|
|
39278
|
+
if (noncurrentLoans !== void 0 && noncurrentLoans > 3) {
|
|
39279
|
+
signals.push(
|
|
39280
|
+
`Noncurrent loans ratio is elevated at ${noncurrentLoans.toFixed(2)}%.`
|
|
39281
|
+
);
|
|
39282
|
+
}
|
|
39283
|
+
if (brokeredDeposits !== void 0 && brokeredDeposits > 0) {
|
|
39284
|
+
signals.push(
|
|
39285
|
+
"Brokered deposit fields are present; review funding-profile analysis for reliance context."
|
|
39286
|
+
);
|
|
39287
|
+
}
|
|
39288
|
+
return signals;
|
|
39289
|
+
}
|
|
39290
|
+
function buildDashboardText(institution, repdte, riskSignals, warnings) {
|
|
39291
|
+
const lines = [
|
|
39292
|
+
`FDIC Bank Deep Dive: ${asString2(institution.NAME)} (CERT ${asString2(institution.CERT)})`,
|
|
39293
|
+
`${asString2(institution.CITY)}, ${asString2(institution.STALP)} | Report date: ${repdte}`,
|
|
39294
|
+
"This dashboard uses public FDIC BankFind data and is not an official CAMELS rating or supervisory conclusion.",
|
|
39295
|
+
"",
|
|
39296
|
+
`Assets: ${asString2(institution.ASSET) || "n/a"} ($thousands)`,
|
|
39297
|
+
`Deposits: ${asString2(institution.DEP) || "n/a"} ($thousands)`,
|
|
39298
|
+
`Offices: ${asString2(institution.OFFICES) || "n/a"}`
|
|
39299
|
+
];
|
|
39300
|
+
if (riskSignals.length > 0) {
|
|
39301
|
+
lines.push("", "Risk signals:", ...riskSignals.map((signal) => `- ${signal}`));
|
|
39302
|
+
}
|
|
39303
|
+
if (warnings.length > 0) {
|
|
39304
|
+
lines.push("", "Warnings:", ...warnings.map((warning) => `- ${warning}`));
|
|
39305
|
+
}
|
|
39306
|
+
return lines.join("\n");
|
|
39307
|
+
}
|
|
39308
|
+
function registerChatGptBankDeepDiveTool(server) {
|
|
39309
|
+
server.registerTool(
|
|
39310
|
+
"fdic_show_bank_deep_dive",
|
|
39311
|
+
{
|
|
39312
|
+
title: "Show Bank Deep Dive Dashboard",
|
|
39313
|
+
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.",
|
|
39314
|
+
inputSchema: BankDeepDiveInputSchema,
|
|
39315
|
+
outputSchema: FdicBankDeepDiveOutputSchema,
|
|
39316
|
+
annotations: {
|
|
39317
|
+
readOnlyHint: true,
|
|
39318
|
+
destructiveHint: false,
|
|
39319
|
+
idempotentHint: true,
|
|
39320
|
+
openWorldHint: true
|
|
39321
|
+
},
|
|
39322
|
+
_meta: {
|
|
39323
|
+
ui: { resourceUri: BANK_DEEP_DIVE_WIDGET_URI },
|
|
39324
|
+
"openai/outputTemplate": BANK_DEEP_DIVE_WIDGET_URI,
|
|
39325
|
+
"openai/toolInvocation/invoking": "Building bank dashboard...",
|
|
39326
|
+
"openai/toolInvocation/invoked": "Bank dashboard ready"
|
|
39327
|
+
}
|
|
39328
|
+
},
|
|
39329
|
+
async (rawParams) => {
|
|
39330
|
+
const repdte = rawParams.repdte ?? getDefaultReportDate();
|
|
39331
|
+
const dateError = validateQuarterEndDate(repdte, "repdte");
|
|
39332
|
+
if (dateError) {
|
|
39333
|
+
return formatToolError(new Error(dateError));
|
|
39334
|
+
}
|
|
39335
|
+
try {
|
|
39336
|
+
const [institutionResponse, financialsResponse] = await Promise.all([
|
|
39337
|
+
queryEndpoint(ENDPOINTS.INSTITUTIONS, {
|
|
39338
|
+
filters: `CERT:${rawParams.cert}`,
|
|
39339
|
+
fields: "CERT,NAME,CITY,STALP,STNAME,ACTIVE,ASSET,DEP,OFFICES,BKCLASS,REGAGNT,ESTYMD",
|
|
39340
|
+
limit: 1
|
|
39341
|
+
}),
|
|
39342
|
+
queryEndpoint(ENDPOINTS.FINANCIALS, {
|
|
39343
|
+
filters: `CERT:${rawParams.cert} AND REPDTE:${repdte}`,
|
|
39344
|
+
fields: "CERT,REPDTE,ASSET,DEP,ROA,ROE,IDT1CER,NCLNLSR,LNLSDEPR,NIMY,EEFFR",
|
|
39345
|
+
limit: 1
|
|
39346
|
+
})
|
|
39347
|
+
]);
|
|
39348
|
+
const institution = extractRecords(institutionResponse)[0];
|
|
39349
|
+
if (!institution) {
|
|
39350
|
+
return formatToolError(
|
|
39351
|
+
new Error(`No institution found with CERT number ${rawParams.cert}.`)
|
|
39352
|
+
);
|
|
39353
|
+
}
|
|
39354
|
+
const financials = extractRecords(financialsResponse)[0];
|
|
39355
|
+
const warnings = financials ? [] : [
|
|
39356
|
+
`No financial record found for CERT ${rawParams.cert} at ${repdte}. Try an earlier quarter-end date.`
|
|
39357
|
+
];
|
|
39358
|
+
const riskSignals = collectDashboardRiskSignals(financials);
|
|
39359
|
+
const structuredContent = {
|
|
39360
|
+
institution: {
|
|
39361
|
+
cert: rawParams.cert,
|
|
39362
|
+
name: asString2(institution.NAME),
|
|
39363
|
+
city: asString2(institution.CITY),
|
|
39364
|
+
state: asString2(institution.STALP),
|
|
39365
|
+
active: institution.ACTIVE === 1 || institution.ACTIVE === "1",
|
|
39366
|
+
asset_thousands: asNumber3(financials?.ASSET) ?? asNumber3(institution.ASSET),
|
|
39367
|
+
deposit_thousands: asNumber3(financials?.DEP) ?? asNumber3(institution.DEP),
|
|
39368
|
+
offices: asNumber3(institution.OFFICES),
|
|
39369
|
+
charter_class: asString2(institution.BKCLASS),
|
|
39370
|
+
regulator: asString2(institution.REGAGNT),
|
|
39371
|
+
established: asString2(institution.ESTYMD),
|
|
39372
|
+
report_date: repdte
|
|
39373
|
+
},
|
|
39374
|
+
assessment: {
|
|
39375
|
+
official_rating: false,
|
|
39376
|
+
proxy_band: riskSignals.length > 0 ? "review" : "no major public flags",
|
|
39377
|
+
caveat: "Public off-site analytical dashboard; not an official CAMELS rating or confidential supervisory conclusion."
|
|
39378
|
+
},
|
|
39379
|
+
metrics: {
|
|
39380
|
+
roa: formatRatio(financials?.ROA),
|
|
39381
|
+
roe: formatRatio(financials?.ROE),
|
|
39382
|
+
tier1_leverage: formatRatio(financials?.IDT1CER),
|
|
39383
|
+
noncurrent_loans: formatRatio(financials?.NCLNLSR),
|
|
39384
|
+
loan_to_deposit: formatRatio(financials?.LNLSDEPR),
|
|
39385
|
+
net_interest_margin: formatRatio(financials?.NIMY),
|
|
39386
|
+
efficiency_ratio: formatRatio(financials?.EEFFR)
|
|
39387
|
+
},
|
|
39388
|
+
risk_signals: riskSignals,
|
|
39389
|
+
warnings,
|
|
39390
|
+
sources: [
|
|
39391
|
+
{
|
|
39392
|
+
title: "FDIC BankFind institution profile",
|
|
39393
|
+
url: getInstitutionUrl(rawParams.cert)
|
|
39394
|
+
}
|
|
39395
|
+
]
|
|
39396
|
+
};
|
|
39397
|
+
return {
|
|
39398
|
+
content: [
|
|
39399
|
+
{
|
|
39400
|
+
type: "text",
|
|
39401
|
+
text: truncateIfNeeded(
|
|
39402
|
+
buildDashboardText(institution, repdte, riskSignals, warnings),
|
|
39403
|
+
CHARACTER_LIMIT
|
|
39404
|
+
)
|
|
39405
|
+
}
|
|
39406
|
+
],
|
|
39407
|
+
structuredContent,
|
|
39408
|
+
_meta: {
|
|
39409
|
+
widget: {
|
|
39410
|
+
resourceUri: BANK_DEEP_DIVE_WIDGET_URI
|
|
39411
|
+
},
|
|
39412
|
+
raw: {
|
|
39413
|
+
institution,
|
|
39414
|
+
financials: financials ?? null
|
|
39415
|
+
}
|
|
39416
|
+
}
|
|
39417
|
+
};
|
|
39418
|
+
} catch (err) {
|
|
39419
|
+
return formatToolError(err);
|
|
39420
|
+
}
|
|
39421
|
+
}
|
|
39422
|
+
);
|
|
39423
|
+
}
|
|
39424
|
+
|
|
38628
39425
|
// src/resources/schemaResources.ts
|
|
38629
39426
|
var RESOURCE_SCHEME = "fdic";
|
|
38630
39427
|
var INDEX_URI = `${RESOURCE_SCHEME}://schemas/index`;
|
|
@@ -38695,33 +39492,218 @@ function registerSchemaResources(server) {
|
|
|
38695
39492
|
}
|
|
38696
39493
|
}
|
|
38697
39494
|
|
|
39495
|
+
// src/prompts/workflows.ts
|
|
39496
|
+
var import_zod23 = require("zod");
|
|
39497
|
+
var BankDeepDiveArgs = {
|
|
39498
|
+
bank: import_zod23.z.string().min(1).describe("Bank name or FDIC Certificate Number (CERT)."),
|
|
39499
|
+
repdte: import_zod23.z.string().regex(/^\d{8}$/).optional().describe(
|
|
39500
|
+
"Optional quarter-end report date in YYYYMMDD format (0331, 0630, 0930, or 1231)."
|
|
39501
|
+
)
|
|
39502
|
+
};
|
|
39503
|
+
var FailureForensicsArgs = {
|
|
39504
|
+
bank: import_zod23.z.string().min(1).describe("Failed bank name or FDIC Certificate Number (CERT)."),
|
|
39505
|
+
lookback_quarters: import_zod23.z.string().regex(/^\d+$/).optional().describe(
|
|
39506
|
+
"Number of pre-failure quarters to reconstruct (default 12 if omitted)."
|
|
39507
|
+
)
|
|
39508
|
+
};
|
|
39509
|
+
var PortfolioSurveillanceArgs = {
|
|
39510
|
+
scope: import_zod23.z.string().min(1).describe(
|
|
39511
|
+
"Universe to screen \u2014 e.g., 'state:NC', 'asset_min:1000000,asset_max:10000000', or a comma-separated CERT list ('certs:3511,29846,...')."
|
|
39512
|
+
),
|
|
39513
|
+
repdte: import_zod23.z.string().regex(/^\d{8}$/).optional().describe("Optional quarter-end report date in YYYYMMDD format.")
|
|
39514
|
+
};
|
|
39515
|
+
var ExaminerOverlayArgs = {
|
|
39516
|
+
bank: import_zod23.z.string().min(1).describe("Bank name or FDIC Certificate Number (CERT)."),
|
|
39517
|
+
qualitative_notes: import_zod23.z.string().optional().describe(
|
|
39518
|
+
"Optional qualitative analyst inputs (management quality, governance, exam findings) to overlay onto the public proxy assessment."
|
|
39519
|
+
)
|
|
39520
|
+
};
|
|
39521
|
+
function userText(text) {
|
|
39522
|
+
return {
|
|
39523
|
+
role: "user",
|
|
39524
|
+
content: { type: "text", text }
|
|
39525
|
+
};
|
|
39526
|
+
}
|
|
39527
|
+
function registerWorkflowPrompts(server) {
|
|
39528
|
+
server.registerPrompt(
|
|
39529
|
+
"bank_deep_dive",
|
|
39530
|
+
{
|
|
39531
|
+
title: "Comprehensive Bank Deep Dive",
|
|
39532
|
+
description: "Produce a comprehensive single-institution analysis report (health, financials, peer benchmarking, credit concentration, funding profile, securities, franchise footprint, regional context).",
|
|
39533
|
+
argsSchema: BankDeepDiveArgs
|
|
39534
|
+
},
|
|
39535
|
+
({ bank, repdte }) => ({
|
|
39536
|
+
messages: [
|
|
39537
|
+
userText(
|
|
39538
|
+
[
|
|
39539
|
+
`Run a comprehensive FDIC bank deep dive for "${bank}"${repdte ? ` as of ${repdte}` : ""}.`,
|
|
39540
|
+
"",
|
|
39541
|
+
"Steps:",
|
|
39542
|
+
"1. If a CERT was not given, call fdic_search_institutions to resolve the bank to a CERT.",
|
|
39543
|
+
"2. Call fdic_analyze_bank_health and fdic_ubpr_analysis for the resolved CERT.",
|
|
39544
|
+
"3. Call fdic_peer_group_analysis to benchmark the institution against peers.",
|
|
39545
|
+
"4. Call fdic_analyze_credit_concentration, fdic_analyze_funding_profile, and fdic_analyze_securities_portfolio.",
|
|
39546
|
+
"5. Call fdic_franchise_footprint for branch/deposit geography and fdic_regional_context for the home market.",
|
|
39547
|
+
"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.",
|
|
39548
|
+
"",
|
|
39549
|
+
"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."
|
|
39550
|
+
].join("\n")
|
|
39551
|
+
)
|
|
39552
|
+
]
|
|
39553
|
+
})
|
|
39554
|
+
);
|
|
39555
|
+
server.registerPrompt(
|
|
39556
|
+
"failure_forensics",
|
|
39557
|
+
{
|
|
39558
|
+
title: "Failed Bank Forensics",
|
|
39559
|
+
description: "Reconstruct the pre-failure financial timeline of a failed FDIC institution and identify the earliest visible warning signals.",
|
|
39560
|
+
argsSchema: FailureForensicsArgs
|
|
39561
|
+
},
|
|
39562
|
+
({ bank, lookback_quarters }) => ({
|
|
39563
|
+
messages: [
|
|
39564
|
+
userText(
|
|
39565
|
+
[
|
|
39566
|
+
`Run a failure-forensics post-mortem for "${bank}" using the FDIC tools.`,
|
|
39567
|
+
"",
|
|
39568
|
+
"Steps:",
|
|
39569
|
+
"1. Resolve the bank to a CERT via fdic_search_institutions or fdic_search.",
|
|
39570
|
+
"2. Call fdic_get_institution_failure to confirm failure date, resolution type, and cost.",
|
|
39571
|
+
`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.`,
|
|
39572
|
+
"4. Call fdic_analyze_credit_concentration, fdic_analyze_funding_profile, and fdic_analyze_securities_portfolio at the latest available pre-failure quarter.",
|
|
39573
|
+
"5. Call fdic_search_history for structural-change events (mergers, charter conversions, assistance) leading up to the failure.",
|
|
39574
|
+
"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.",
|
|
39575
|
+
"",
|
|
39576
|
+
"Highlight inflection points (e.g. quarter ROA turned negative, when noncurrent loans exceeded reserves). Reference each finding to the report date that supports it."
|
|
39577
|
+
].join("\n")
|
|
39578
|
+
)
|
|
39579
|
+
]
|
|
39580
|
+
})
|
|
39581
|
+
);
|
|
39582
|
+
server.registerPrompt(
|
|
39583
|
+
"portfolio_surveillance",
|
|
39584
|
+
{
|
|
39585
|
+
title: "Portfolio Surveillance Watchlist",
|
|
39586
|
+
description: "Screen a universe of FDIC institutions and produce a decision-ready watchlist tiered Escalate / Monitor / No Immediate Concern.",
|
|
39587
|
+
argsSchema: PortfolioSurveillanceArgs
|
|
39588
|
+
},
|
|
39589
|
+
({ scope, repdte }) => ({
|
|
39590
|
+
messages: [
|
|
39591
|
+
userText(
|
|
39592
|
+
[
|
|
39593
|
+
`Run portfolio surveillance over scope: "${scope}"${repdte ? ` as of ${repdte}` : ""}.`,
|
|
39594
|
+
"",
|
|
39595
|
+
"Steps:",
|
|
39596
|
+
"1. Build the institution roster:",
|
|
39597
|
+
" - state:<XX> \u2192 fdic_search_institutions filters STALP:<XX> AND ACTIVE:1",
|
|
39598
|
+
" - asset_min:<n>,asset_max:<n> \u2192 ASSET range",
|
|
39599
|
+
" - certs:<csv> \u2192 use the comma-separated list directly",
|
|
39600
|
+
"2. Call fdic_detect_risk_signals across the roster.",
|
|
39601
|
+
"3. Call fdic_compare_peer_health to rank by composite proxy band.",
|
|
39602
|
+
"4. For the highest-risk subset, call fdic_analyze_bank_health for full proxy assessments.",
|
|
39603
|
+
"5. Tier institutions:",
|
|
39604
|
+
" - Escalate: critical risk signals or proxy band 'unsatisfactory'.",
|
|
39605
|
+
" - Monitor: warning signals or 'fair' band, especially deteriorating trends.",
|
|
39606
|
+
" - No Immediate Concern: no critical signals, satisfactory or strong band.",
|
|
39607
|
+
"6. Produce a watchlist table per tier (CERT, name, key signals, rationale).",
|
|
39608
|
+
"",
|
|
39609
|
+
"Treat all scoring as a public off-site analytical proxy. Surface data caveats explicitly."
|
|
39610
|
+
].join("\n")
|
|
39611
|
+
)
|
|
39612
|
+
]
|
|
39613
|
+
})
|
|
39614
|
+
);
|
|
39615
|
+
server.registerPrompt(
|
|
39616
|
+
"examiner_overlay",
|
|
39617
|
+
{
|
|
39618
|
+
title: "Examiner Overlay Assessment",
|
|
39619
|
+
description: "Layer qualitative analyst/examiner inputs on top of the public CAMELS proxy and produce a blended assessment with explicit provenance.",
|
|
39620
|
+
argsSchema: ExaminerOverlayArgs
|
|
39621
|
+
},
|
|
39622
|
+
({ bank, qualitative_notes }) => ({
|
|
39623
|
+
messages: [
|
|
39624
|
+
userText(
|
|
39625
|
+
[
|
|
39626
|
+
`Produce an examiner-overlay assessment for "${bank}".`,
|
|
39627
|
+
"",
|
|
39628
|
+
"Steps:",
|
|
39629
|
+
"1. Resolve to a CERT via fdic_search_institutions if needed.",
|
|
39630
|
+
"2. Call fdic_analyze_bank_health to get the public_camels_proxy_v1 baseline (capital, asset quality, earnings, liquidity, sensitivity).",
|
|
39631
|
+
"3. Call fdic_ubpr_analysis for performance ratios and fdic_analyze_funding_profile / fdic_analyze_credit_concentration for sub-component depth.",
|
|
39632
|
+
"4. Combine the public baseline with qualitative analyst inputs:",
|
|
39633
|
+
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.)",
|
|
39634
|
+
"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.",
|
|
39635
|
+
"6. Final composite must reconcile both columns and call out any divergence.",
|
|
39636
|
+
"",
|
|
39637
|
+
"Disclaimer: this overlay is not an official CAMELS rating or confidential supervisory conclusion."
|
|
39638
|
+
].join("\n")
|
|
39639
|
+
)
|
|
39640
|
+
]
|
|
39641
|
+
})
|
|
39642
|
+
);
|
|
39643
|
+
}
|
|
39644
|
+
|
|
38698
39645
|
// src/index.ts
|
|
38699
|
-
function
|
|
39646
|
+
function resolveProfile(raw) {
|
|
39647
|
+
const tokens = (raw ?? "all").split(",").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
|
|
39648
|
+
const has = (token) => tokens.includes(token);
|
|
39649
|
+
const all = has("all") || tokens.length === 0;
|
|
39650
|
+
const includeChatgpt = all || has("chatgpt");
|
|
39651
|
+
return {
|
|
39652
|
+
core: all || has("core"),
|
|
39653
|
+
analysis: all || has("analysis"),
|
|
39654
|
+
chatgptCanonical: all || includeChatgpt || has("chatgpt-canonical"),
|
|
39655
|
+
chatgptAliases: all || includeChatgpt || has("chatgpt-aliases"),
|
|
39656
|
+
chatgptDeepDive: all || includeChatgpt,
|
|
39657
|
+
prompts: all || has("prompts"),
|
|
39658
|
+
resources: all || has("resources")
|
|
39659
|
+
};
|
|
39660
|
+
}
|
|
39661
|
+
function createServer(options = {}) {
|
|
38700
39662
|
const server = new import_mcp.McpServer({
|
|
38701
39663
|
name: "fdic-mcp-server",
|
|
38702
39664
|
version: VERSION
|
|
38703
39665
|
});
|
|
38704
|
-
|
|
38705
|
-
|
|
38706
|
-
|
|
38707
|
-
|
|
38708
|
-
|
|
38709
|
-
|
|
38710
|
-
|
|
38711
|
-
|
|
38712
|
-
|
|
38713
|
-
|
|
38714
|
-
|
|
38715
|
-
|
|
38716
|
-
|
|
38717
|
-
|
|
38718
|
-
|
|
38719
|
-
|
|
38720
|
-
|
|
38721
|
-
|
|
38722
|
-
|
|
38723
|
-
|
|
38724
|
-
|
|
39666
|
+
const profile = resolveProfile(options.profile ?? process.env.FDIC_MCP_PROFILE);
|
|
39667
|
+
if (profile.core) {
|
|
39668
|
+
registerInstitutionTools(server);
|
|
39669
|
+
registerFailureTools(server);
|
|
39670
|
+
registerLocationTools(server);
|
|
39671
|
+
registerHistoryTools(server);
|
|
39672
|
+
registerFinancialTools(server);
|
|
39673
|
+
registerSodTools(server);
|
|
39674
|
+
registerDemographicsTools(server);
|
|
39675
|
+
}
|
|
39676
|
+
if (profile.analysis) {
|
|
39677
|
+
registerAnalysisTools(server);
|
|
39678
|
+
registerPeerGroupTools(server);
|
|
39679
|
+
registerBankHealthTools(server);
|
|
39680
|
+
registerPeerHealthTools(server);
|
|
39681
|
+
registerRiskSignalTools(server);
|
|
39682
|
+
registerCreditConcentrationTools(server);
|
|
39683
|
+
registerFundingProfileTools(server);
|
|
39684
|
+
registerSecuritiesPortfolioTools(server);
|
|
39685
|
+
registerUbprAnalysisTools(server);
|
|
39686
|
+
registerMarketShareAnalysisTools(server);
|
|
39687
|
+
registerFranchiseFootprintTools(server);
|
|
39688
|
+
registerHoldingCompanyProfileTools(server);
|
|
39689
|
+
registerRegionalContextTools(server);
|
|
39690
|
+
}
|
|
39691
|
+
if (profile.chatgptCanonical || profile.chatgptAliases) {
|
|
39692
|
+
registerChatGptRetrievalTools(server, {
|
|
39693
|
+
includeCanonicalNames: profile.chatgptCanonical,
|
|
39694
|
+
includeNamespacedAliases: profile.chatgptAliases
|
|
39695
|
+
});
|
|
39696
|
+
}
|
|
39697
|
+
if (profile.chatgptDeepDive) {
|
|
39698
|
+
registerChatGptBankDeepDiveTool(server);
|
|
39699
|
+
registerChatGptAppResources(server);
|
|
39700
|
+
}
|
|
39701
|
+
if (profile.resources) {
|
|
39702
|
+
registerSchemaResources(server);
|
|
39703
|
+
}
|
|
39704
|
+
if (profile.prompts) {
|
|
39705
|
+
registerWorkflowPrompts(server);
|
|
39706
|
+
}
|
|
38725
39707
|
return server;
|
|
38726
39708
|
}
|
|
38727
39709
|
async function runStdio() {
|
|
@@ -38793,23 +39775,61 @@ function sendInvalidSessionResponse(res) {
|
|
|
38793
39775
|
}
|
|
38794
39776
|
function createApp(options = {}) {
|
|
38795
39777
|
const app = (0, import_express2.default)();
|
|
38796
|
-
const serverFactory = options.serverFactory ?? createServer;
|
|
39778
|
+
const serverFactory = options.serverFactory ?? (() => createServer());
|
|
38797
39779
|
const port = options.port ?? 3e3;
|
|
38798
39780
|
const allowedOrigins = options.allowedOrigins ?? parseAllowedOrigins(void 0, port);
|
|
38799
39781
|
const sessions = /* @__PURE__ */ new Map();
|
|
38800
39782
|
const chatSessions = /* @__PURE__ */ new Map();
|
|
38801
39783
|
const sessionIdleTimeoutMs = options.sessionIdleTimeoutMs ?? DEFAULT_SESSION_IDLE_TIMEOUT_MS;
|
|
38802
39784
|
const sessionSweepIntervalMs = options.sessionSweepIntervalMs ?? DEFAULT_SESSION_SWEEP_INTERVAL_MS;
|
|
39785
|
+
const stateless = options.stateless ?? process.env.FDIC_MCP_STATELESS_HTTP === "true";
|
|
38803
39786
|
app.use(import_express2.default.json());
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
|
|
38807
|
-
|
|
38808
|
-
|
|
39787
|
+
if (!stateless) {
|
|
39788
|
+
const sessionSweepTimer = setInterval(() => {
|
|
39789
|
+
void sweepIdleSessions(sessions, sessionIdleTimeoutMs, Date.now());
|
|
39790
|
+
sweepIdleChatSessions(chatSessions, sessionIdleTimeoutMs, Date.now());
|
|
39791
|
+
}, sessionSweepIntervalMs);
|
|
39792
|
+
sessionSweepTimer.unref?.();
|
|
39793
|
+
}
|
|
38809
39794
|
app.get("/health", (_req, res) => {
|
|
38810
39795
|
res.json({ status: "ok", server: "fdic-mcp-server", version: VERSION });
|
|
38811
39796
|
});
|
|
38812
39797
|
app.all("/mcp", async (req, res) => {
|
|
39798
|
+
if (stateless) {
|
|
39799
|
+
let server;
|
|
39800
|
+
let transport;
|
|
39801
|
+
try {
|
|
39802
|
+
server = serverFactory();
|
|
39803
|
+
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
39804
|
+
sessionIdGenerator: void 0,
|
|
39805
|
+
enableJsonResponse: true,
|
|
39806
|
+
enableDnsRebindingProtection: true,
|
|
39807
|
+
allowedOrigins
|
|
39808
|
+
});
|
|
39809
|
+
res.on("close", () => {
|
|
39810
|
+
void transport?.close().catch(() => {
|
|
39811
|
+
});
|
|
39812
|
+
void server?.close().catch(() => {
|
|
39813
|
+
});
|
|
39814
|
+
});
|
|
39815
|
+
await server.connect(transport);
|
|
39816
|
+
await transport.handleRequest(req, res, req.body);
|
|
39817
|
+
} catch (error) {
|
|
39818
|
+
console.error("MCP request error:", error);
|
|
39819
|
+
if (!res.headersSent) {
|
|
39820
|
+
res.status(500).json({
|
|
39821
|
+
jsonrpc: "2.0",
|
|
39822
|
+
error: { code: -32603, message: "Internal server error" },
|
|
39823
|
+
id: null
|
|
39824
|
+
});
|
|
39825
|
+
}
|
|
39826
|
+
await transport?.close().catch(() => {
|
|
39827
|
+
});
|
|
39828
|
+
await server?.close().catch(() => {
|
|
39829
|
+
});
|
|
39830
|
+
}
|
|
39831
|
+
return;
|
|
39832
|
+
}
|
|
38813
39833
|
const sessionIdHeader = req.headers["mcp-session-id"];
|
|
38814
39834
|
const sessionId = typeof sessionIdHeader === "string" ? sessionIdHeader : void 0;
|
|
38815
39835
|
try {
|