Skip to main content
Glama
cashAccounts.ts9.05 kB
/** * @file Cash Accounts Resource Handler * @version 1.0.0 * @description Handles Brex cash accounts resource requests */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { ResourceTemplate } from "../models/resourceTemplate.js"; import { logDebug, logError } from "../utils/logger.js"; import { BrexClient } from "../services/brex/client.js"; import { isCashAccount, isStatement } from "../services/brex/transactions-types.js"; import { parseQueryParams } from "../models/common.js"; import { estimateTokens } from "../utils/responseLimiter.js"; // Get Brex client function getBrexClient(): BrexClient { return new BrexClient(); } // Define resource templates const cashAccountsTemplate = new ResourceTemplate("brex://accounts/cash{/id}"); const cashAccountsStatementsTemplate = new ResourceTemplate("brex://accounts/cash/{id}/statements"); // const primaryCashAccountTemplate = new ResourceTemplate("brex://accounts/cash/primary"); /** * Registers the cash accounts resource handler with the server * @param server The MCP server instance */ export function registerCashAccountsResource(server: Server): void { server.registerCapabilities({ resources: { "brex://accounts/cash{/id}": { description: "Brex cash accounts", mimeTypes: ["application/json"], }, "brex://accounts/cash/primary": { description: "Brex primary cash account", mimeTypes: ["application/json"], }, "brex://accounts/cash/{id}/statements": { description: "Brex cash account statements", mimeTypes: ["application/json"], } } }); // Use the standard approach with setRequestHandler server.setRequestHandler(ReadResourceRequestSchema, async (request, _extra) => { const uri = request.params.uri; // Check if this handler should process this URI if (!uri.startsWith("brex://accounts/cash")) { return { handled: false }; // Not handled by this handler } logDebug(`Reading cash account resource: ${uri}`); // Get Brex client const brexClient = getBrexClient(); // Primary cash account endpoint if (uri.includes("cash/primary") && !uri.includes("statements")) { try { logDebug("Fetching primary cash account from Brex API"); const account = await brexClient.getPrimaryCashAccount(); // Validate account if (!isCashAccount(account)) { logError(`Invalid primary cash account data received: ${JSON.stringify(account)}`); throw new Error('Invalid primary cash account data received'); } logDebug(`Successfully fetched primary cash account`); return { contents: [{ uri: uri, mimeType: "application/json", text: JSON.stringify(account, null, 2) }] }; } catch (error) { logError(`Failed to fetch primary cash account: ${error instanceof Error ? error.message : String(error)}`); throw error; } } // Handle statements endpoint if (uri.includes("/statements")) { const statementsParams = cashAccountsStatementsTemplate.parse(uri); if (!statementsParams.id) { return { error: { message: "Account ID is required for statements endpoint", code: 400 } }; } // Extract cursor and limit from query parameters const qp = parseQueryParams(uri); const cursor = qp.cursor || undefined; const limit = qp.limit ? parseInt(qp.limit, 10) : undefined; try { logDebug(`Fetching statements for cash account ${statementsParams.id} from Brex API`); const statements = await brexClient.getCashAccountStatements(statementsParams.id, cursor, limit); // Validate statements if (!statements.items || !Array.isArray(statements.items)) { throw new Error('Invalid statements data received'); } for (const statement of statements.items) { if (!isStatement(statement)) { logError(`Invalid statement data received: ${JSON.stringify(statement)}`); throw new Error('Invalid statement data received'); } } logDebug(`Successfully fetched ${statements.items.length} statements for cash account ${statementsParams.id}`); // Projection/limiting const stmtFields = qp.fields ? qp.fields.split(',').map(s => s.trim()).filter(Boolean) : undefined; const stmtSummaryOnly = qp.summary_only === 'true'; const stmtTooBig = estimateTokens(JSON.stringify(statements.items)) > 24000; const stmtSummarized = stmtSummaryOnly || stmtTooBig; const stmtProjected = stmtSummarized && stmtFields && stmtFields.length ? statements.items.map(t => project(t, stmtFields)) : (stmtSummarized ? statements.items.map(t => project(t, DEFAULT_STMT_FIELDS)) : statements.items); // Format response with pagination information const result = { items: stmtProjected, pagination: { hasMore: !!(statements as any).next_cursor, nextCursor: (statements as any).next_cursor }, meta: { summary_applied: stmtSummarized } }; return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(result, null, 2) }] }; } catch (error) { logError(`Failed to fetch cash account statements: ${error instanceof Error ? error.message : String(error)}`); throw error; } } // Handle regular cash accounts endpoint const params = cashAccountsTemplate.parse(uri); if (!params.id) { // List all cash accounts try { logDebug("Fetching all cash accounts from Brex API"); const accounts = await brexClient.getCashAccounts(); // Validate accounts if (!accounts.items || !Array.isArray(accounts.items)) { throw new Error('Invalid cash accounts data received'); } for (const account of accounts.items) { if (!isCashAccount(account)) { logError(`Invalid cash account data received: ${JSON.stringify(account)}`); throw new Error('Invalid cash account data received'); } } logDebug(`Successfully fetched ${accounts.items.length} cash accounts`); // Format response with pagination information const result = { items: accounts.items, pagination: { hasMore: !!accounts.next_cursor, nextCursor: accounts.next_cursor } }; return { contents: [{ uri: uri, mimeType: "application/json", text: JSON.stringify(result, null, 2) }] }; } catch (error) { logError(`Failed to fetch cash accounts: ${error instanceof Error ? error.message : String(error)}`); throw error; } } else { // Get specific cash account by ID try { logDebug(`Fetching cash account ${params.id} from Brex API`); const account = await brexClient.getCashAccountById(params.id); // Validate account if (!isCashAccount(account)) { logError(`Invalid cash account data received for account ID: ${params.id}`); throw new Error('Invalid cash account data received'); } logDebug(`Successfully fetched cash account ${params.id}`); const qp = parseQueryParams(uri); const fields = qp.fields ? qp.fields.split(',').map(s => s.trim()).filter(Boolean) : undefined; const summaryOnly = qp.summary_only === 'true'; const tooBig = estimateTokens(JSON.stringify(account)) > 24000; const summarized = summaryOnly || tooBig; const projected = summarized && fields && fields.length ? project(account, fields) : account; return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(projected, null, 2) }] }; } catch (error) { logError(`Failed to fetch cash account ${params.id}: ${error instanceof Error ? error.message : String(error)}`); throw error; } } }); } const DEFAULT_STMT_FIELDS = [ 'id', 'period_start', 'period_end', 'opening_balance.amount', 'opening_balance.currency', 'closing_balance.amount', 'closing_balance.currency' ]; function project(src: any, fields: string[]): any { const out: any = {}; for (const f of fields) { const parts = f.split('.'); let cur: any = src; for (const p of parts) { cur = cur?.[p]; if (cur === undefined) break; } if (cur !== undefined) { let o: any = out; for (let i = 0; i < parts.length - 1; i++) { o[parts[i]] = o[parts[i]] ?? {}; o = o[parts[i]]; } o[parts[parts.length - 1]] = cur; } } return out; }

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/crazyrabbitLTC/mcp-brex-server'

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