Skip to main content
Glama
jflamb

FDIC BankFind MCP Server

by jflamb

fdic_compare_bank_snapshots

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);
          }
        },
      );

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