pocketsmith-mcp
Version:
MCP server for managing budgets via PocketSmith API
158 lines (157 loc) • 6.37 kB
JavaScript
/**
* @fileoverview Get recurring events (scheduled transactions) 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 GetRecurringEventsInputSchema = 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)"),
startDate: z.string().describe("Start date for events range (YYYY-MM-DD)"),
endDate: z.string().describe("End date for events range (YYYY-MM-DD)"),
});
export const GetRecurringEventsResponseSchema = z.object({
events: z.array(z.object({
id: z.string(),
amount: z.number(),
date: z.string(),
repeat_type: z.string(),
repeat_interval: z.number(),
currency_code: z.string().optional(),
colour: z.string().nullable(),
category: z.object({
id: z.number(),
title: z.string(),
}).optional(),
scenario: z.object({
id: z.number(),
title: z.string(),
}).optional(),
note: z.string().nullable(),
is_debit: z.boolean(),
})).describe("List of recurring events"),
summary: z.object({
totalEvents: z.number(),
totalDebitEvents: z.number(),
totalCreditEvents: z.number(),
averageAmount: z.number(),
dateRange: z.string(),
}).describe("Summary of recurring events"),
});
export async function getRecurringEventsLogic(params, context) {
logger.debug("Processing get recurring events 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 recurring events
const events = await service.getRecurringEvents(user.id, params.startDate, params.endDate, context);
// Process events
const processedEvents = events.map(event => ({
id: event.id ?? '',
amount: event.amount ?? 0,
date: event.date ?? '',
repeat_type: event.repeat_type ?? '',
repeat_interval: event.repeat_interval ?? 1,
currency_code: event.currency_code,
colour: event.colour ?? null,
category: event.category ? {
id: event.category.id ?? 0,
title: event.category.title ?? '',
} : undefined,
scenario: event.scenario ? {
id: event.scenario.id ?? 0,
title: event.scenario.title ?? '',
} : undefined,
note: event.note ?? null,
is_debit: event.amount ? event.amount < 0 : false,
}));
// Calculate summary
const totalEvents = processedEvents.length;
const debitEvents = processedEvents.filter(e => e.is_debit);
const creditEvents = processedEvents.filter(e => !e.is_debit);
const averageAmount = totalEvents > 0
? processedEvents.reduce((sum, e) => sum + Math.abs(e.amount), 0) / totalEvents
: 0;
const response = {
events: processedEvents,
summary: {
totalEvents,
totalDebitEvents: debitEvents.length,
totalCreditEvents: creditEvents.length,
averageAmount,
dateRange: `${params.startDate} to ${params.endDate}`,
},
};
logger.debug("Get recurring events processed successfully", {
...context,
eventCount: events.length,
debitCount: debitEvents.length,
creditCount: creditEvents.length,
});
return response;
}
export const registerGetRecurringEventsTool = async (server) => {
const toolName = "get_recurring_events";
const toolDescription = "Get recurring events (scheduled transactions) from PocketSmith for a date range";
const registrationContext = requestContextService.createRequestContext({
operation: "RegisterTool",
toolName: toolName,
});
logger.info(`Registering tool: '${toolName}'`, registrationContext);
await ErrorHandler.tryCatch(async () => {
server.registerTool(toolName, {
title: "Get PocketSmith Recurring Events",
description: toolDescription,
inputSchema: GetRecurringEventsInputSchema.shape,
outputSchema: GetRecurringEventsResponseSchema.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 getRecurringEventsLogic(params, handlerContext);
return {
structuredContent: result,
content: [
{ type: "text", text: JSON.stringify(result, null, 2) },
],
};
}
catch (error) {
const mcpError = ErrorHandler.handleError(error, {
operation: "getRecurringEventsHandler",
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,
});
};