Skip to main content
Glama
jflamb

FDIC BankFind MCP Server

by jflamb

Compare Bank Snapshot Trends

fdic_compare_bank_snapshots
Read-onlyIdempotent

Compare FDIC bank financial snapshots to analyze growth, profitability, or efficiency changes across institutions over time.

Instructions

Compare FDIC reporting snapshots across a set of institutions and rank the results by growth, profitability, or efficiency changes.

This tool is designed for heavier analytical prompts that would otherwise require many separate MCP calls. It batches institution roster lookup, financial snapshots, optional office-count snapshots, and can also fetch a quarterly time series inside the server.

Good uses:

  • Identify North Carolina banks with the strongest asset growth from 2021 to 2025

  • Compare whether deposit growth came with branch expansion or profitability improvement

  • Rank a specific cert list by ROA, ROE, asset-per-office, or deposit-to-asset changes

  • Pull a quarterly trend series and highlight inflection points, streaks, and structural shifts

Inputs:

  • state or certs: choose a geographic roster or provide a direct comparison set

  • start_repdte, end_repdte: Report Dates (REPDTE) in YYYYMMDD format — must be quarter-end dates (0331, 0630, 0930, 1231)

  • analysis_mode: snapshot or timeseries

  • institution_filters: optional extra institution filter when building the roster

  • active_only: default true

  • include_demographics: default true, adds office-count comparisons when available

  • sort_by: ranking field (default: asset_growth). All 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

  • sort_order: ASC or DESC

  • limit: maximum ranked results to return

Returns concise comparison text plus structured deltas, derived metrics, and insight tags for each institution.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stateNoState name for the institution roster filter. Example: "North Carolina"
certsNoOptional list of FDIC certificate numbers to compare directly. Max 100.
institution_filtersNoAdditional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"
active_onlyNoLimit the comparison set to currently active institutions.
start_repdteNoStarting 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.
end_repdteNoEnding 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).
analysis_modeNoUse snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range.snapshot
include_demographicsNoInclude office-count changes from the demographics dataset when available.
limitNoMaximum number of ranked comparisons to return.
sort_byNoComparison 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.asset_growth
sort_orderNoSort direction for the ranked comparisons.DESC

Implementation Reference

  • The handler function for the `fdic_compare_bank_snapshots` tool, which orchestrates data fetching, metrics calculation, and formatting for bank snapshot analysis.
      async (rawParams, extra) => {
        const {
          state,
          certs,
          institution_filters,
          active_only,
          start_repdte,
          end_repdte,
          analysis_mode,
          include_demographics,
          limit,
          sort_by,
          sort_order,
        } = resolveSnapshotDefaults(rawParams);
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
        const progressToken = extra._meta?.progressToken;
    
        try {
          const validationError = validateSnapshotAnalysisParams({
            state,
            certs,
            institution_filters,
            active_only,
            start_repdte,
            end_repdte,
            analysis_mode,
            include_demographics,
            limit,
            sort_by,
            sort_order,
          });
          if (validationError) {
            return formatToolError(new Error(validationError));
          }
    
          await sendProgressNotification(
            server.server,
            progressToken,
            0.1,
            "Fetching institution roster",
          );
    
          const rosterResult =
            certs && certs.length > 0
              ? {
                  records: certs.map((cert) => ({ CERT: cert })),
                  warning: undefined,
                }
              : await fetchInstitutionRoster(
                  state,
                  institution_filters,
                  active_only,
                  controller.signal,
                );
          const roster = rosterResult.records;
          const warnings = rosterResult.warning ? [rosterResult.warning] : [];
    
          const candidateCerts = roster
            .map((record) => asNumber(record.CERT))
            .filter((cert): cert is number => cert !== null);
    
          if (candidateCerts.length === 0) {
            const output = buildAnalysisOutput({
              totalCandidates: 0,
              analyzedCount: 0,
              startRepdte: start_repdte,
              endRepdte: end_repdte,
              analysisMode: analysis_mode,
              sortBy: sort_by,
              sortOrder: sort_order,
              warnings,
              comparisons: [],
              limitCount: 0,
            });
    
            return {
              content: [
                { type: "text", text: "No institutions matched the comparison set." },
              ],
              structuredContent: output,
            };
          }
    
          const rosterByCert = new Map(
            roster
              .map((record) => [asNumber(record.CERT), record] as const)
              .filter(
                (entry): entry is readonly [number, InstitutionRecord] =>
                  entry[0] !== null,
              ),
          );
    
          let comparisons: ComparisonRecord[] = [];
    
          if (analysis_mode === "timeseries") {
            await sendProgressNotification(
              server.server,
              progressToken,
              0.3,
              include_demographics
                ? "Fetching financial and demographic time series"
                : "Fetching financial time series",
            );
    
            const [financialSeriesResult, demographicsSeriesResult] = await Promise.all([
              fetchSeriesRecords(
                ENDPOINTS.FINANCIALS,
                candidateCerts,
                start_repdte,
                end_repdte,
                "CERT,NAME,REPDTE,ASSET,DEP,NETINC,ROA,ROE",
                controller.signal,
              ),
              include_demographics
                ? fetchSeriesRecords(
                    ENDPOINTS.DEMOGRAPHICS,
                    candidateCerts,
                    start_repdte,
                    end_repdte,
                    "CERT,REPDTE,OFFTOT,OFFSTATE,CBSANAME",
                    controller.signal,
                  )
                : Promise.resolve({
                    grouped: new Map<number, InstitutionRecord[]>(),
                    warnings: [],
                  }),
            ]);
            warnings.push(
              ...financialSeriesResult.warnings,
              ...demographicsSeriesResult.warnings,
            );
    
            await sendProgressNotification(
              server.server,
              progressToken,
              0.9,
              "Computing metrics and insights",
            );
    
            const financialSeries = financialSeriesResult.grouped;
            const demographicsSeries = demographicsSeriesResult.grouped;
    
            comparisons = candidateCerts
              .map((cert) =>
                summarizeTimeSeries(
                  financialSeries.get(cert) ?? [],
                  new Map(
                    (demographicsSeries.get(cert) ?? []).map((record) => [
                      String(record.REPDTE),
                      record,
                    ]),
                  ),
                  rosterByCert.get(cert) ?? {},
                ),
              )
              .filter((comparison): comparison is ComparisonRecord => comparison !== null);
          } else {
            await sendProgressNotification(
              server.server,
              progressToken,
              0.3,
              include_demographics
                ? "Fetching financial and demographic snapshots"
                : "Fetching financial snapshots",
            );
    
            const [financialSnapshotsResult, demographicSnapshotsResult] = await Promise.all([
              fetchBatchedRecordsForDates(
                ENDPOINTS.FINANCIALS,
                candidateCerts,
                [`REPDTE:${start_repdte}`, `REPDTE:${end_repdte}`],
                "CERT,NAME,REPDTE,ASSET,DEP,NETINC,ROA,ROE",
                controller.signal,
              ),
              include_demographics
                ? fetchBatchedRecordsForDates(
                    ENDPOINTS.DEMOGRAPHICS,
                    candidateCerts,
                    [`REPDTE:${start_repdte}`, `REPDTE:${end_repdte}`],
                    "CERT,REPDTE,OFFTOT,OFFSTATE,CBSANAME",
                    controller.signal,
                  )
                : Promise.resolve({
                    byDate: new Map<string, Map<number, InstitutionRecord>>(),
                    warnings: [],
                  }),
            ]);
            warnings.push(
              ...financialSnapshotsResult.warnings,
              ...demographicSnapshotsResult.warnings,
            );
    
            await sendProgressNotification(
              server.server,
              progressToken,
              0.9,
              "Computing metrics and insights",
            );
    
            const financialSnapshots = financialSnapshotsResult.byDate;
            const demographicSnapshots = demographicSnapshotsResult.byDate;
    
            const startFinancials =
              financialSnapshots.get(`REPDTE:${start_repdte}`) ??
              new Map<number, InstitutionRecord>();
            const endFinancials =
              financialSnapshots.get(`REPDTE:${end_repdte}`) ??
              new Map<number, InstitutionRecord>();
            const startDemographics =
              demographicSnapshots.get(`REPDTE:${start_repdte}`) ??
              new Map<number, InstitutionRecord>();
            const endDemographics =
              demographicSnapshots.get(`REPDTE:${end_repdte}`) ??
              new Map<number, InstitutionRecord>();
    
            comparisons = candidateCerts
              .map((cert) => {
                const startFinancial = startFinancials.get(cert);
                const endFinancial = endFinancials.get(cert);
                if (!startFinancial || !endFinancial) return null;
                return buildSnapshotComparison(
                  cert,
                  rosterByCert.get(cert) ?? {},
                  startFinancial,
                  endFinancial,
                  startDemographics.get(cert),
                  endDemographics.get(cert),
                  start_repdte,
                  end_repdte,
                );
              })
              .filter((comparison): comparison is ComparisonRecord => comparison !== null);
          }
    
          const sortedComparisons = sortComparisons(
            comparisons,
            sort_by,
            sort_order,
          );
          const ranked = sortedComparisons.slice(0, limit);
          const output = buildAnalysisOutput({
            totalCandidates: candidateCerts.length,
            analyzedCount: comparisons.length,
            startRepdte: start_repdte,
            endRepdte: end_repdte,
            analysisMode: analysis_mode,
            sortBy: sort_by,
            sortOrder: sort_order,
            warnings,
            comparisons: ranked,
            insightComparisons: sortedComparisons,
            limitCount: ranked.length,
          });
          const textOutput = {
            ...output,
            insights: buildTopLevelInsights(ranked),
          };
    
          const text = truncateIfNeeded(
            [
              ...warnings.map((warning) => `Warning: ${warning}`),
              formatComparisonText(textOutput),
            ]
              .filter((value): value is string => value !== null)
              .join("\n\n"),
            CHARACTER_LIMIT,
            "Reduce the number of certs, narrow institution_filters, request fewer fields, or shorten the date range.",
          );
    
          await sendProgressNotification(
            server.server,
            progressToken,
            1,
            "Analysis complete",
          );
    
          return {
            content: [{ type: "text", text }],
            structuredContent: output,
          };
        } catch (err) {
          if (controller.signal.aborted) {
            return formatToolError(
              new Error(
                `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1000)} seconds. ` +
                  `Try reducing the comparison set: use certs (max 100) instead of a state-wide roster, add institution_filters (e.g., BKCLASS:N), or shorten the date range for timeseries mode.`,
              ),
            );
          }
          return formatToolError(err);
        } finally {
          clearTimeout(timeoutId);
        }
      },
    );
  • Input schema definition for the `fdic_compare_bank_snapshots` tool.
    const SnapshotAnalysisSchema = z.object({
        state: z
          .string()
          .optional()
          .describe(
            'State name for the institution roster filter. Example: "North Carolina"',
          ),
        certs: z
          .array(z.number().int().positive())
          .max(100)
          .optional()
          .describe(
            "Optional list of FDIC certificate numbers to compare directly. Max 100.",
          ),
        institution_filters: z
          .string()
          .optional()
          .describe(
            'Additional institution-level filter used when building the comparison set. Example: BKCLASS:N or CITY:"Charlotte"',
          ),
        active_only: z
          .boolean()
          .default(true)
          .describe("Limit the comparison set to currently active institutions."),
        start_repdte: z
          .string()
          .regex(/^\d{8}$/)
          .optional()
          .describe(
            "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.",
          ),
        end_repdte: z
          .string()
          .regex(/^\d{8}$/)
          .optional()
          .describe(
            "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).",
          ),
        analysis_mode: AnalysisModeSchema.default("snapshot").describe(
          "Use snapshot for two-point comparison or timeseries for quarterly trend analysis across the date range.",
        ),
        include_demographics: z
          .boolean()
          .default(true)
          .describe(
            "Include office-count changes from the demographics dataset when available.",
          ),
        limit: z
          .number()
          .int()
          .min(1)
          .max(100)
          .default(10)
          .describe("Maximum number of ranked comparisons to return."),
        sort_by: SortFieldSchema.default("asset_growth").describe(
          "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.",
        ),
        sort_order: z
          .enum(["ASC", "DESC"])
          .default("DESC")
          .describe("Sort direction for the ranked comparisons."),
      });
  • Tool registration for `fdic_compare_bank_snapshots` within the MCP server.
    export function registerAnalysisTools(server: McpServer): void {
      server.registerTool(
        "fdic_compare_bank_snapshots",
        {
          title: "Compare Bank Snapshot Trends",
          description: `Compare FDIC reporting snapshots across a set of institutions and rank the results by growth, profitability, or efficiency changes.
    
    This tool is designed for heavier analytical prompts that would otherwise require many separate MCP calls. It batches institution roster lookup, financial snapshots, optional office-count snapshots, and can also fetch a quarterly time series inside the server.
    
    Good uses:
      - Identify North Carolina banks with the strongest asset growth from 2021 to 2025
      - Compare whether deposit growth came with branch expansion or profitability improvement
      - Rank a specific cert list by ROA, ROE, asset-per-office, or deposit-to-asset changes
      - Pull a quarterly trend series and highlight inflection points, streaks, and structural shifts
    
    Inputs:
      - state or certs: choose a geographic roster or provide a direct comparison set
      - start_repdte, end_repdte: Report Dates (REPDTE) in YYYYMMDD format — must be quarter-end dates (0331, 0630, 0930, 1231)
      - analysis_mode: snapshot or timeseries
      - institution_filters: optional extra institution filter when building the roster
      - active_only: default true
      - include_demographics: default true, adds office-count comparisons when available
      - sort_by: ranking field (default: asset_growth). All 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
      - sort_order: ASC or DESC
      - limit: maximum ranked results to return
    
    Returns concise comparison text plus structured deltas, derived metrics, and insight tags for each institution.`,
          inputSchema: SnapshotAnalysisSchema,
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
        async (rawParams, extra) => {
          const {
            state,
            certs,
            institution_filters,
            active_only,
            start_repdte,
            end_repdte,
            analysis_mode,
            include_demographics,
            limit,
            sort_by,
            sort_order,
          } = resolveSnapshotDefaults(rawParams);
          const controller = new AbortController();
          const timeoutId = setTimeout(() => controller.abort(), ANALYSIS_TIMEOUT_MS);
          const progressToken = extra._meta?.progressToken;
    
          try {
            const validationError = validateSnapshotAnalysisParams({
              state,
              certs,
              institution_filters,
              active_only,
              start_repdte,
              end_repdte,
              analysis_mode,
              include_demographics,
              limit,
              sort_by,
              sort_order,
            });
            if (validationError) {
              return formatToolError(new Error(validationError));
            }
    
            await sendProgressNotification(
              server.server,
              progressToken,
              0.1,
              "Fetching institution roster",
            );
    
            const rosterResult =
              certs && certs.length > 0
                ? {
                    records: certs.map((cert) => ({ CERT: cert })),
                    warning: undefined,
                  }
                : await fetchInstitutionRoster(
                    state,
                    institution_filters,
                    active_only,
                    controller.signal,
                  );
            const roster = rosterResult.records;
            const warnings = rosterResult.warning ? [rosterResult.warning] : [];
    
            const candidateCerts = roster
              .map((record) => asNumber(record.CERT))
              .filter((cert): cert is number => cert !== null);
    
            if (candidateCerts.length === 0) {
              const output = buildAnalysisOutput({
                totalCandidates: 0,
                analyzedCount: 0,
                startRepdte: start_repdte,
                endRepdte: end_repdte,
                analysisMode: analysis_mode,
                sortBy: sort_by,
                sortOrder: sort_order,
                warnings,
                comparisons: [],
                limitCount: 0,
              });
    
              return {
                content: [
                  { type: "text", text: "No institutions matched the comparison set." },
                ],
                structuredContent: output,
              };
            }
    
            const rosterByCert = new Map(
              roster
                .map((record) => [asNumber(record.CERT), record] as const)
                .filter(
                  (entry): entry is readonly [number, InstitutionRecord] =>
                    entry[0] !== null,
                ),
            );
    
            let comparisons: ComparisonRecord[] = [];
    
            if (analysis_mode === "timeseries") {
              await sendProgressNotification(
                server.server,
                progressToken,
                0.3,
                include_demographics
                  ? "Fetching financial and demographic time series"
                  : "Fetching financial time series",
              );
    
              const [financialSeriesResult, demographicsSeriesResult] = await Promise.all([
                fetchSeriesRecords(
                  ENDPOINTS.FINANCIALS,
                  candidateCerts,
                  start_repdte,
                  end_repdte,
                  "CERT,NAME,REPDTE,ASSET,DEP,NETINC,ROA,ROE",
                  controller.signal,
                ),
                include_demographics
                  ? fetchSeriesRecords(
                      ENDPOINTS.DEMOGRAPHICS,
                      candidateCerts,
                      start_repdte,
                      end_repdte,
                      "CERT,REPDTE,OFFTOT,OFFSTATE,CBSANAME",
                      controller.signal,
                    )
                  : Promise.resolve({
                      grouped: new Map<number, InstitutionRecord[]>(),
                      warnings: [],
                    }),
              ]);
              warnings.push(
                ...financialSeriesResult.warnings,
                ...demographicsSeriesResult.warnings,
              );
    
              await sendProgressNotification(
                server.server,
                progressToken,
                0.9,
                "Computing metrics and insights",
              );
    
              const financialSeries = financialSeriesResult.grouped;
              const demographicsSeries = demographicsSeriesResult.grouped;
    
              comparisons = candidateCerts
                .map((cert) =>
                  summarizeTimeSeries(
                    financialSeries.get(cert) ?? [],
                    new Map(
                      (demographicsSeries.get(cert) ?? []).map((record) => [
                        String(record.REPDTE),
                        record,
                      ]),
                    ),
                    rosterByCert.get(cert) ?? {},
                  ),
                )
                .filter((comparison): comparison is ComparisonRecord => comparison !== null);
            } else {
              await sendProgressNotification(
                server.server,
                progressToken,
                0.3,
                include_demographics
                  ? "Fetching financial and demographic snapshots"
                  : "Fetching financial snapshots",
              );
    
              const [financialSnapshotsResult, demographicSnapshotsResult] = await Promise.all([
                fetchBatchedRecordsForDates(
                  ENDPOINTS.FINANCIALS,
                  candidateCerts,
                  [`REPDTE:${start_repdte}`, `REPDTE:${end_repdte}`],
                  "CERT,NAME,REPDTE,ASSET,DEP,NETINC,ROA,ROE",
                  controller.signal,
                ),
                include_demographics
                  ? fetchBatchedRecordsForDates(
                      ENDPOINTS.DEMOGRAPHICS,
                      candidateCerts,
                      [`REPDTE:${start_repdte}`, `REPDTE:${end_repdte}`],
                      "CERT,REPDTE,OFFTOT,OFFSTATE,CBSANAME",
                      controller.signal,
                    )
                  : Promise.resolve({
                      byDate: new Map<string, Map<number, InstitutionRecord>>(),
                      warnings: [],
                    }),
              ]);
              warnings.push(
                ...financialSnapshotsResult.warnings,
                ...demographicSnapshotsResult.warnings,
              );
    
              await sendProgressNotification(
                server.server,
                progressToken,
                0.9,
                "Computing metrics and insights",
              );
    
              const financialSnapshots = financialSnapshotsResult.byDate;
              const demographicSnapshots = demographicSnapshotsResult.byDate;
    
              const startFinancials =
                financialSnapshots.get(`REPDTE:${start_repdte}`) ??
                new Map<number, InstitutionRecord>();
              const endFinancials =
                financialSnapshots.get(`REPDTE:${end_repdte}`) ??
                new Map<number, InstitutionRecord>();
              const startDemographics =
                demographicSnapshots.get(`REPDTE:${start_repdte}`) ??
                new Map<number, InstitutionRecord>();
              const endDemographics =
                demographicSnapshots.get(`REPDTE:${end_repdte}`) ??
                new Map<number, InstitutionRecord>();
    
              comparisons = candidateCerts
                .map((cert) => {
                  const startFinancial = startFinancials.get(cert);
                  const endFinancial = endFinancials.get(cert);
                  if (!startFinancial || !endFinancial) return null;
                  return buildSnapshotComparison(
                    cert,
                    rosterByCert.get(cert) ?? {},
                    startFinancial,
                    endFinancial,
                    startDemographics.get(cert),
                    endDemographics.get(cert),
                    start_repdte,
                    end_repdte,
                  );
                })
                .filter((comparison): comparison is ComparisonRecord => comparison !== null);
            }
    
            const sortedComparisons = sortComparisons(
              comparisons,
              sort_by,
              sort_order,
            );
            const ranked = sortedComparisons.slice(0, limit);
            const output = buildAnalysisOutput({
              totalCandidates: candidateCerts.length,
              analyzedCount: comparisons.length,
              startRepdte: start_repdte,
              endRepdte: end_repdte,
              analysisMode: analysis_mode,
              sortBy: sort_by,
              sortOrder: sort_order,
              warnings,
              comparisons: ranked,
              insightComparisons: sortedComparisons,
              limitCount: ranked.length,
            });
            const textOutput = {
              ...output,
              insights: buildTopLevelInsights(ranked),
            };
    
            const text = truncateIfNeeded(
              [
                ...warnings.map((warning) => `Warning: ${warning}`),
                formatComparisonText(textOutput),
              ]
                .filter((value): value is string => value !== null)
                .join("\n\n"),
              CHARACTER_LIMIT,
              "Reduce the number of certs, narrow institution_filters, request fewer fields, or shorten the date range.",
            );
    
            await sendProgressNotification(
              server.server,
              progressToken,
              1,
              "Analysis complete",
            );
    
            return {
              content: [{ type: "text", text }],
              structuredContent: output,
            };
          } catch (err) {
            if (controller.signal.aborted) {
              return formatToolError(
                new Error(
                  `Analysis timed out after ${Math.floor(ANALYSIS_TIMEOUT_MS / 1000)} seconds. ` +
                    `Try reducing the comparison set: use certs (max 100) instead of a state-wide roster, add institution_filters (e.g., BKCLASS:N), or shorten the date range for timeseries mode.`,
                ),
              );
            }
            return formatToolError(err);
          } finally {
            clearTimeout(timeoutId);
          }
        },
      );
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false, openWorldHint=true, and idempotentHint=true, covering safety and idempotency. The description adds valuable context: it explains the tool batches operations (institution roster lookup, financial snapshots, optional office-count snapshots, quarterly time series), mentions server-side processing, and specifies date constraints ('must be quarter-end dates'). It does not contradict annotations, and the added context justifies a score above baseline.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (purpose, usage examples, inputs, returns) and is appropriately sized for a complex tool. It is front-loaded with the core purpose. Some sentences in the 'Good uses' section could be more concise, but overall, it efficiently conveys necessary information without significant waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (11 parameters, no output schema) and rich annotations, the description is complete. It explains the tool's purpose, usage scenarios, inputs, and return format ('concise comparison text plus structured deltas, derived metrics, and insight tags'), compensating for the lack of output schema. The examples and constraints provide sufficient context for an agent to use it effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description includes an 'Inputs' section that lists parameters but adds minimal semantic value beyond the schema (e.g., it notes date format constraints already in schema). No new parameter insights are provided, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Compare FDIC reporting snapshots across a set of institutions and rank the results by growth, profitability, or efficiency changes.' It specifies the verb ('compare'), resource ('FDIC reporting snapshots'), and scope ('across a set of institutions'), distinguishing it from siblings like fdic_search_financials (search) or fdic_peer_group_analysis (group analysis).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool: 'for heavier analytical prompts that would otherwise require many separate MCP calls.' It includes a 'Good uses' section with specific examples (e.g., 'Identify North Carolina banks with the strongest asset growth'), clearly differentiating it from simpler search or lookup tools. It also mentions batching capabilities, which helps the agent choose this over multiple separate calls.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jflamb/fdic-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server