UNPKG

actual-mcp

Version:

Actual Budget MCP server exposing API functionality

252 lines 7.41 kB
import api from '@actual-app/api'; import fs from 'fs'; import path from 'path'; import os from 'os'; const DEFAULT_DATA_DIR = path.resolve(os.homedir() || '.', '.actual'); // API initialization state let initialized = false; let initializing = false; let initializationError = null; /** * Initialize the Actual Budget API */ export async function initActualApi() { if (initialized) return; if (initializing) { // Wait for initialization to complete if already in progress while (initializing) { await new Promise((resolve) => setTimeout(resolve, 100)); } if (initializationError) throw initializationError; return; } initializing = true; try { console.error('Initializing Actual Budget API...'); const dataDir = process.env.ACTUAL_DATA_DIR || DEFAULT_DATA_DIR; if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } const serverURL = process.env.ACTUAL_SERVER_URL; const password = process.env.ACTUAL_PASSWORD; // Reason: InitConfig is a discriminated union in 26.x — NoServerConfig forbids serverURL/password const initConfig = serverURL ? { dataDir, serverURL, password: password ?? '' } : { dataDir }; await api.init(initConfig); const budgets = await api.getBudgets(); if (!budgets || budgets.length === 0) { throw new Error('No budgets found. Please create a budget in Actual first.'); } // Use specified budget or the first one const budgetId = process.env.ACTUAL_BUDGET_SYNC_ID || budgets[0].cloudFileId || budgets[0].id || ''; console.error(`Loading budget: ${budgetId}`); await api.downloadBudget(budgetId, process.env.ACTUAL_BUDGET_ENCRYPTION_PASSWORD ? { password: process.env.ACTUAL_BUDGET_ENCRYPTION_PASSWORD, } : undefined); initialized = true; console.error('Actual Budget API initialized successfully'); } catch (error) { console.error('Failed to initialize Actual Budget API:', error); initializationError = error instanceof Error ? error : new Error(String(error)); throw initializationError; } finally { initializing = false; } } /** * Shutdown the Actual Budget API */ export async function shutdownActualApi() { if (!initialized) return; try { await api.shutdown(); } catch (err) { console.error('Error shutting down Actual Budget API:', err); } finally { initialized = false; } } // ---------------------------- // FETCH // ---------------------------- /** * Get all accounts (ensures API is initialized) */ export async function getAccounts() { await initActualApi(); return api.getAccounts(); } /** * Get all categories (ensures API is initialized) */ export async function getCategories() { await initActualApi(); return api.getCategories(); } /** * Get all category groups (ensures API is initialized) */ export async function getCategoryGroups() { await initActualApi(); return api.getCategoryGroups(); } /** * Get all payees (ensures API is initialized) */ export async function getPayees() { await initActualApi(); return api.getPayees(); } /** * Get transactions for a specific account and date range (ensures API is initialized) */ export async function getTransactions(accountId, start, end) { await initActualApi(); return api.getTransactions(accountId, start, end); } /** * Get all rules (ensures API is initialized) */ export async function getRules() { await initActualApi(); return api.getRules(); } // ---------------------------- // ACTION // ---------------------------- /** * Create a new payee (ensures API is initialized) */ export async function createPayee(args) { await initActualApi(); return api.createPayee(args); } /** * Update a payee (ensures API is initialized) */ export async function updatePayee(id, args) { await initActualApi(); return api.updatePayee(id, args); } /** * Delete a payee (ensures API is initialized) */ export async function deletePayee(id) { await initActualApi(); return api.deletePayee(id); } /** * Create a new rule (ensures API is initialized) */ export async function createRule(args) { await initActualApi(); return api.createRule(args); } /** * Update a rule (ensures API is initialized) */ export async function updateRule(args) { await initActualApi(); return api.updateRule(args); } /** * Delete a rule (ensures API is initialized) */ export async function deleteRule(id) { await initActualApi(); return api.deleteRule(id); } /** * Create a new category (ensures API is initialized) */ export async function createCategory(args) { await initActualApi(); return api.createCategory(args); } /** * Update a category (ensures API is initialized) */ export async function updateCategory(id, args) { await initActualApi(); return api.updateCategory(id, args); } /** * Delete a category (ensures API is initialized) */ export async function deleteCategory(id) { await initActualApi(); return api.deleteCategory(id); } /** * Create a new category group (ensures API is initialized) */ export async function createCategoryGroup(args) { await initActualApi(); return api.createCategoryGroup(args); } /** * Update a category group (ensures API is initialized) */ export async function updateCategoryGroup(id, args) { await initActualApi(); return api.updateCategoryGroup(id, args); } /** * Delete a category group (ensures API is initialized) */ export async function deleteCategoryGroup(id) { await initActualApi(); return api.deleteCategoryGroup(id); } /** * Create a transaction (ensures API is initialized). * Passes runTransfers so that transfer payees automatically create the counterpart transaction. */ export async function createTransaction(accountId, data) { await initActualApi(); return api.addTransactions(accountId, [data], { runTransfers: true }); } /** * Import a list of transactions using Actual's reconciliation logic. * Deduplicates via imported_id and optionally supports dry-run validation. */ export async function importTransactions(accountId, transactions, opts) { await initActualApi(); return api.importTransactions(accountId, transactions, opts); } /** * Update a transaction (ensures API is initialized) */ export async function updateTransaction(id, data) { await initActualApi(); return api.updateTransaction(id, data); } /** * Delete a transaction (ensures API is initialized) */ export async function deleteTransaction(id) { await initActualApi(); return api.deleteTransaction(id); } /** * Run bank sync for accounts (ensures API is initialized) * * @param accountId - Optional. Specific account ID, or special value: * - "onbudget": sync all on-budget linked accounts * - "offbudget": sync all off-budget linked accounts * - undefined: sync ALL linked accounts */ export async function runBankSync(accountId) { await initActualApi(); // API expects { accountId } object or undefined for all accounts return api.runBankSync(accountId ? { accountId } : undefined); } //# sourceMappingURL=actual-api.js.map