UNPKG

pocketsmith-mcp

Version:

MCP server for managing budgets via PocketSmith API

154 lines (153 loc) 6.47 kB
/** * @fileoverview Get user's transaction accounts from PocketSmith */ import { z } from "zod"; import { BaseErrorCode } from "../../../types-global/errors.js"; import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js"; import { PocketSmithService } from "../../../services/pocketsmith.js"; export const GetTransactionAccountsInputSchema = z.object({ apiKey: z.string().optional().describe("PocketSmith API key (if not set via environment)"), accessToken: z.string().optional().describe("OAuth access token (if not using API key)"), }); export const GetTransactionAccountsResponseSchema = z.object({ transactionAccounts: z.array(z.object({ id: z.number(), name: z.string(), number: z.string().nullable(), current_balance: z.number(), current_balance_date: z.string(), current_balance_in_base_currency: z.number(), safe_balance: z.number().nullable(), safe_balance_in_base_currency: z.number().nullable(), starting_balance: z.number(), starting_balance_date: z.string(), currency_code: z.string(), type: z.string(), institution: z.object({ id: z.number(), title: z.string(), }).optional(), created_at: z.string().optional(), updated_at: z.string().optional(), })).describe("List of user's transaction accounts"), summary: z.object({ totalAccounts: z.number(), totalBalance: z.number(), accountsByType: z.record(z.string(), z.number()), }).describe("Summary of transaction accounts"), }); export async function getTransactionAccountsLogic(params, context) { logger.debug("Processing get transaction accounts request", { ...context, toolInput: params, }); const apiKey = params.apiKey || process.env.POCKETSMITH_API_KEY; const accessToken = params.accessToken || process.env.POCKETSMITH_ACCESS_TOKEN; const service = new PocketSmithService(apiKey, accessToken); // Get current user first const user = await service.getCurrentUser(context); // Get transaction accounts const transactionAccounts = await service.getTransactionAccounts(user.id, context); // Process transaction accounts const processedAccounts = transactionAccounts.map(account => ({ id: account.id ?? 0, name: account.name ?? '', number: account.number ?? null, current_balance: account.current_balance ?? 0, current_balance_date: account.current_balance_date ?? '', current_balance_in_base_currency: account.current_balance_in_base_currency ?? 0, safe_balance: account.safe_balance ?? null, safe_balance_in_base_currency: account.safe_balance_in_base_currency ?? null, starting_balance: account.starting_balance ?? 0, starting_balance_date: account.starting_balance_date ?? '', currency_code: account.currency_code ?? '', type: account.type ?? '', institution: account.institution ? { id: account.institution.id ?? 0, title: account.institution.title ?? '', } : undefined, created_at: account.created_at, updated_at: account.updated_at, })); // Calculate summary const totalBalance = processedAccounts.reduce((sum, account) => { return sum + account.current_balance_in_base_currency; }, 0); const accountsByType = {}; processedAccounts.forEach(account => { accountsByType[account.type] = (accountsByType[account.type] || 0) + 1; }); const response = { transactionAccounts: processedAccounts, summary: { totalAccounts: transactionAccounts.length, totalBalance, accountsByType, }, }; logger.debug("Get transaction accounts processed successfully", { ...context, accountCount: transactionAccounts.length, totalBalance, }); return response; } export const registerGetTransactionAccountsTool = async (server) => { const toolName = "get_transaction_accounts"; const toolDescription = "Get user's transaction accounts from PocketSmith with detailed information"; const registrationContext = requestContextService.createRequestContext({ operation: "RegisterTool", toolName: toolName, }); logger.info(`Registering tool: '${toolName}'`, registrationContext); await ErrorHandler.tryCatch(async () => { server.registerTool(toolName, { title: "Get PocketSmith Transaction Accounts", description: toolDescription, inputSchema: GetTransactionAccountsInputSchema.shape, outputSchema: GetTransactionAccountsResponseSchema.shape, annotations: { readOnlyHint: true, openWorldHint: false, }, }, async (params) => { const handlerContext = requestContextService.createRequestContext({ parentRequestId: registrationContext.requestId, operation: "HandleToolRequest", toolName: toolName, input: params, }); try { const result = await getTransactionAccountsLogic(params, handlerContext); return { structuredContent: result, content: [ { type: "text", text: JSON.stringify(result, null, 2) }, ], }; } catch (error) { const mcpError = ErrorHandler.handleError(error, { operation: "getTransactionAccountsHandler", context: handlerContext, input: params, }); return { isError: true, content: [{ type: "text", text: `Error: ${mcpError.message}` }], structuredContent: { code: mcpError.code, message: mcpError.message, details: mcpError.details, }, }; } }); logger.info(`Tool '${toolName}' registered successfully.`, registrationContext); }, { operation: `RegisteringTool_${toolName}`, context: registrationContext, errorCode: BaseErrorCode.INITIALIZATION_FAILED, critical: true, }); };