actual-mcp-server 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +663 -0
- package/bin/actual-mcp-server.js +3 -0
- package/dist/generated/actual-client/types.js +5 -0
- package/dist/package.json +88 -0
- package/dist/src/actualConnection.js +157 -0
- package/dist/src/actualToolsManager.js +211 -0
- package/dist/src/auth/budget-acl.js +143 -0
- package/dist/src/auth/setup.js +58 -0
- package/dist/src/config.js +41 -0
- package/dist/src/index.js +313 -0
- package/dist/src/lib/ActualConnectionPool.js +343 -0
- package/dist/src/lib/ActualMCPConnection.js +125 -0
- package/dist/src/lib/actual-adapter.js +1228 -0
- package/dist/src/lib/actual-schema.js +222 -0
- package/dist/src/lib/budget-registry.js +64 -0
- package/dist/src/lib/constants.js +121 -0
- package/dist/src/lib/errors.js +19 -0
- package/dist/src/lib/loggerFactory.js +72 -0
- package/dist/src/lib/node-polyfills.js +20 -0
- package/dist/src/lib/query-validator.js +221 -0
- package/dist/src/lib/retry.js +26 -0
- package/dist/src/lib/schemas/common.js +203 -0
- package/dist/src/lib/toolFactory.js +109 -0
- package/dist/src/logger.js +127 -0
- package/dist/src/observability.js +58 -0
- package/dist/src/prompts/showLargeTransactions.js +6 -0
- package/dist/src/resources/accountsSummary.js +13 -0
- package/dist/src/server/httpServer.js +540 -0
- package/dist/src/server/httpServer_testing.js +401 -0
- package/dist/src/server/stdioServer.js +52 -0
- package/dist/src/server/streamable-http.js +148 -0
- package/dist/src/tests/actualToolsTests.js +70 -0
- package/dist/src/tests/observability.smoke.test.js +18 -0
- package/dist/src/tests/testMcpClient.js +170 -0
- package/dist/src/tests_adapter_runner.js +86 -0
- package/dist/src/tools/accounts_close.js +16 -0
- package/dist/src/tools/accounts_create.js +27 -0
- package/dist/src/tools/accounts_delete.js +16 -0
- package/dist/src/tools/accounts_get_balance.js +40 -0
- package/dist/src/tools/accounts_list.js +16 -0
- package/dist/src/tools/accounts_reopen.js +16 -0
- package/dist/src/tools/accounts_update.js +52 -0
- package/dist/src/tools/bank_sync.js +22 -0
- package/dist/src/tools/budget_updates_batch.js +77 -0
- package/dist/src/tools/budgets_getMonth.js +14 -0
- package/dist/src/tools/budgets_getMonths.js +14 -0
- package/dist/src/tools/budgets_get_all.js +13 -0
- package/dist/src/tools/budgets_holdForNextMonth.js +19 -0
- package/dist/src/tools/budgets_list_available.js +20 -0
- package/dist/src/tools/budgets_resetHold.js +16 -0
- package/dist/src/tools/budgets_setAmount.js +26 -0
- package/dist/src/tools/budgets_setCarryover.js +18 -0
- package/dist/src/tools/budgets_switch.js +27 -0
- package/dist/src/tools/budgets_transfer.js +64 -0
- package/dist/src/tools/categories_create.js +65 -0
- package/dist/src/tools/categories_delete.js +16 -0
- package/dist/src/tools/categories_get.js +14 -0
- package/dist/src/tools/categories_update.js +22 -0
- package/dist/src/tools/category_groups_create.js +18 -0
- package/dist/src/tools/category_groups_delete.js +26 -0
- package/dist/src/tools/category_groups_get.js +13 -0
- package/dist/src/tools/category_groups_update.js +21 -0
- package/dist/src/tools/get_id_by_name.js +36 -0
- package/dist/src/tools/index.js +63 -0
- package/dist/src/tools/payee_rules_get.js +27 -0
- package/dist/src/tools/payees_create.js +25 -0
- package/dist/src/tools/payees_delete.js +16 -0
- package/dist/src/tools/payees_get.js +14 -0
- package/dist/src/tools/payees_merge.js +17 -0
- package/dist/src/tools/payees_update.js +59 -0
- package/dist/src/tools/query_run.js +78 -0
- package/dist/src/tools/rules_create.js +129 -0
- package/dist/src/tools/rules_create_or_update.js +191 -0
- package/dist/src/tools/rules_delete.js +26 -0
- package/dist/src/tools/rules_get.js +13 -0
- package/dist/src/tools/rules_update.js +120 -0
- package/dist/src/tools/schedules_create.js +54 -0
- package/dist/src/tools/schedules_delete.js +41 -0
- package/dist/src/tools/schedules_get.js +13 -0
- package/dist/src/tools/schedules_update.js +40 -0
- package/dist/src/tools/server_get_version.js +22 -0
- package/dist/src/tools/server_info.js +86 -0
- package/dist/src/tools/session_close.js +100 -0
- package/dist/src/tools/session_list.js +24 -0
- package/dist/src/tools/transactions_create.js +50 -0
- package/dist/src/tools/transactions_delete.js +20 -0
- package/dist/src/tools/transactions_filter.js +73 -0
- package/dist/src/tools/transactions_get.js +23 -0
- package/dist/src/tools/transactions_import.js +21 -0
- package/dist/src/tools/transactions_search_by_amount.js +126 -0
- package/dist/src/tools/transactions_search_by_category.js +137 -0
- package/dist/src/tools/transactions_search_by_month.js +142 -0
- package/dist/src/tools/transactions_search_by_payee.js +142 -0
- package/dist/src/tools/transactions_summary_by_category.js +80 -0
- package/dist/src/tools/transactions_summary_by_payee.js +72 -0
- package/dist/src/tools/transactions_uncategorized.js +66 -0
- package/dist/src/tools/transactions_update.js +34 -0
- package/dist/src/tools/transactions_update_batch.js +60 -0
- package/dist/src/utils.js +63 -0
- package/package.json +88 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actual Budget Database Schema
|
|
3
|
+
*
|
|
4
|
+
* This module provides the schema definition for Actual Budget's database tables
|
|
5
|
+
* and fields. Used for pre-validation of SQL queries before execution to prevent
|
|
6
|
+
* server crashes from invalid field references.
|
|
7
|
+
*
|
|
8
|
+
* Based on: @actual-app/api schema definition
|
|
9
|
+
* Source: packages/loot-core/src/server/aql/schema/index.ts
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Core Actual Budget database schema
|
|
13
|
+
* Defines all available tables and their fields
|
|
14
|
+
*/
|
|
15
|
+
export const ACTUAL_SCHEMA = {
|
|
16
|
+
transactions: {
|
|
17
|
+
id: { type: 'id' },
|
|
18
|
+
is_parent: { type: 'boolean' },
|
|
19
|
+
is_child: { type: 'boolean' },
|
|
20
|
+
parent_id: { type: 'id' },
|
|
21
|
+
account: { type: 'id', ref: 'accounts', required: true },
|
|
22
|
+
category: { type: 'id', ref: 'categories' },
|
|
23
|
+
amount: { type: 'integer', default: 0, required: true },
|
|
24
|
+
payee: { type: 'id', ref: 'payees' },
|
|
25
|
+
notes: { type: 'string' },
|
|
26
|
+
date: { type: 'date', required: true },
|
|
27
|
+
imported_id: { type: 'string' },
|
|
28
|
+
imported_payee: { type: 'string' },
|
|
29
|
+
error: { type: 'json' },
|
|
30
|
+
cleared: { type: 'boolean' },
|
|
31
|
+
pending: { type: 'boolean' },
|
|
32
|
+
transfer_id: { type: 'id' },
|
|
33
|
+
sort_order: { type: 'float' },
|
|
34
|
+
starting_balance_flag: { type: 'boolean' },
|
|
35
|
+
reconciled: { type: 'boolean' },
|
|
36
|
+
schedule: { type: 'id', ref: 'schedules' },
|
|
37
|
+
tombstone: { type: 'boolean' },
|
|
38
|
+
},
|
|
39
|
+
accounts: {
|
|
40
|
+
id: { type: 'id' },
|
|
41
|
+
name: { type: 'string', required: true },
|
|
42
|
+
type: { type: 'string' },
|
|
43
|
+
offbudget: { type: 'boolean' },
|
|
44
|
+
closed: { type: 'boolean' },
|
|
45
|
+
sort_order: { type: 'float' },
|
|
46
|
+
tombstone: { type: 'boolean' },
|
|
47
|
+
account_id: { type: 'string' },
|
|
48
|
+
official_name: { type: 'string' },
|
|
49
|
+
balance_current: { type: 'integer' },
|
|
50
|
+
balance_available: { type: 'integer' },
|
|
51
|
+
balance_limit: { type: 'integer' },
|
|
52
|
+
mask: { type: 'string' },
|
|
53
|
+
bank: { type: 'id', ref: 'banks' },
|
|
54
|
+
account_sync_source: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
categories: {
|
|
57
|
+
id: { type: 'id' },
|
|
58
|
+
name: { type: 'string', required: true },
|
|
59
|
+
is_income: { type: 'boolean', required: true },
|
|
60
|
+
group: { type: 'id', ref: 'category_groups', required: true },
|
|
61
|
+
sort_order: { type: 'float' },
|
|
62
|
+
tombstone: { type: 'boolean' },
|
|
63
|
+
hidden: { type: 'boolean' },
|
|
64
|
+
goal_def: { type: 'json' },
|
|
65
|
+
},
|
|
66
|
+
category_groups: {
|
|
67
|
+
id: { type: 'id' },
|
|
68
|
+
name: { type: 'string', required: true },
|
|
69
|
+
is_income: { type: 'boolean', required: true },
|
|
70
|
+
sort_order: { type: 'float' },
|
|
71
|
+
tombstone: { type: 'boolean' },
|
|
72
|
+
hidden: { type: 'boolean' },
|
|
73
|
+
},
|
|
74
|
+
payees: {
|
|
75
|
+
id: { type: 'id' },
|
|
76
|
+
name: { type: 'string', required: true },
|
|
77
|
+
category: { type: 'id', ref: 'categories' },
|
|
78
|
+
tombstone: { type: 'boolean' },
|
|
79
|
+
transfer_acct: { type: 'id', ref: 'accounts' },
|
|
80
|
+
},
|
|
81
|
+
schedules: {
|
|
82
|
+
id: { type: 'id' },
|
|
83
|
+
name: { type: 'string' },
|
|
84
|
+
rule: { type: 'id', ref: 'rules', required: true },
|
|
85
|
+
next_date: { type: 'date' },
|
|
86
|
+
completed: { type: 'boolean' },
|
|
87
|
+
posts_transaction: { type: 'boolean' },
|
|
88
|
+
tombstone: { type: 'boolean' },
|
|
89
|
+
_payee: { type: 'id', ref: 'payees' },
|
|
90
|
+
_account: { type: 'id', ref: 'accounts' },
|
|
91
|
+
_amount: { type: 'json/fallback' },
|
|
92
|
+
_amountOp: { type: 'string' },
|
|
93
|
+
_date: { type: 'json/fallback' },
|
|
94
|
+
_conditions: { type: 'json' },
|
|
95
|
+
_actions: { type: 'json' },
|
|
96
|
+
},
|
|
97
|
+
rules: {
|
|
98
|
+
id: { type: 'id' },
|
|
99
|
+
stage: { type: 'string' },
|
|
100
|
+
conditions_op: { type: 'string' },
|
|
101
|
+
conditions: { type: 'json' },
|
|
102
|
+
actions: { type: 'json' },
|
|
103
|
+
tombstone: { type: 'boolean' },
|
|
104
|
+
},
|
|
105
|
+
notes: {
|
|
106
|
+
id: { type: 'id' },
|
|
107
|
+
note: { type: 'string' },
|
|
108
|
+
},
|
|
109
|
+
banks: {
|
|
110
|
+
id: { type: 'id' },
|
|
111
|
+
bank_id: { type: 'string' },
|
|
112
|
+
name: { type: 'string' },
|
|
113
|
+
tombstone: { type: 'boolean' },
|
|
114
|
+
},
|
|
115
|
+
preferences: {
|
|
116
|
+
id: { type: 'id' },
|
|
117
|
+
value: { type: 'string' },
|
|
118
|
+
},
|
|
119
|
+
transaction_filters: {
|
|
120
|
+
id: { type: 'id' },
|
|
121
|
+
name: { type: 'string' },
|
|
122
|
+
conditions_op: { type: 'string' },
|
|
123
|
+
conditions: { type: 'json' },
|
|
124
|
+
tombstone: { type: 'boolean' },
|
|
125
|
+
},
|
|
126
|
+
custom_reports: {
|
|
127
|
+
id: { type: 'id' },
|
|
128
|
+
name: { type: 'string' },
|
|
129
|
+
start_date: { type: 'string', default: '2023-06' },
|
|
130
|
+
end_date: { type: 'string', default: '2023-09' },
|
|
131
|
+
date_static: { type: 'integer', default: 0 },
|
|
132
|
+
date_range: { type: 'string' },
|
|
133
|
+
mode: { type: 'string', default: 'total' },
|
|
134
|
+
group_by: { type: 'string', default: 'Category' },
|
|
135
|
+
sort_by: { type: 'string', default: 'desc' },
|
|
136
|
+
balance_type: { type: 'string', default: 'Expense' },
|
|
137
|
+
show_empty: { type: 'integer', default: 0 },
|
|
138
|
+
show_offbudget: { type: 'integer', default: 0 },
|
|
139
|
+
show_hidden: { type: 'integer', default: 0 },
|
|
140
|
+
show_uncategorized: { type: 'integer', default: 0 },
|
|
141
|
+
trim_intervals: { type: 'integer', default: 0 },
|
|
142
|
+
include_current: { type: 'integer', default: 0 },
|
|
143
|
+
graph_type: { type: 'string', default: 'BarGraph' },
|
|
144
|
+
conditions: { type: 'json' },
|
|
145
|
+
conditions_op: { type: 'string' },
|
|
146
|
+
metadata: { type: 'json' },
|
|
147
|
+
interval: { type: 'string', default: 'Monthly' },
|
|
148
|
+
color_scheme: { type: 'json' },
|
|
149
|
+
tombstone: { type: 'boolean' },
|
|
150
|
+
},
|
|
151
|
+
dashboard: {
|
|
152
|
+
id: { type: 'id' },
|
|
153
|
+
type: { type: 'string', required: true },
|
|
154
|
+
width: { type: 'integer', required: true },
|
|
155
|
+
height: { type: 'integer', required: true },
|
|
156
|
+
x: { type: 'integer', required: true },
|
|
157
|
+
y: { type: 'integer', required: true },
|
|
158
|
+
meta: { type: 'json' },
|
|
159
|
+
tombstone: { type: 'boolean' },
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Common join paths for transactions
|
|
164
|
+
* Format: "field.joinField" maps to referenced table
|
|
165
|
+
*/
|
|
166
|
+
export const JOIN_PATHS = {
|
|
167
|
+
'payee.name': { table: 'payees', field: 'name' },
|
|
168
|
+
'payee.category': { table: 'payees', field: 'category' },
|
|
169
|
+
'category.name': { table: 'categories', field: 'name' },
|
|
170
|
+
'category.group': { table: 'categories', field: 'group' },
|
|
171
|
+
'category.is_income': { table: 'categories', field: 'is_income' },
|
|
172
|
+
'category.hidden': { table: 'categories', field: 'hidden' },
|
|
173
|
+
'account.name': { table: 'accounts', field: 'name' },
|
|
174
|
+
'account.type': { table: 'accounts', field: 'type' },
|
|
175
|
+
'account.offbudget': { table: 'accounts', field: 'offbudget' },
|
|
176
|
+
'account.closed': { table: 'accounts', field: 'closed' },
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Get all valid field names for a table
|
|
180
|
+
*/
|
|
181
|
+
export function getTableFields(tableName) {
|
|
182
|
+
const table = ACTUAL_SCHEMA[tableName];
|
|
183
|
+
if (!table)
|
|
184
|
+
return null;
|
|
185
|
+
return Object.keys(table);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get all valid table names
|
|
189
|
+
*/
|
|
190
|
+
export function getTableNames() {
|
|
191
|
+
return Object.keys(ACTUAL_SCHEMA);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Check if a table exists
|
|
195
|
+
*/
|
|
196
|
+
export function isValidTable(tableName) {
|
|
197
|
+
return tableName in ACTUAL_SCHEMA;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Check if a field exists in a table
|
|
201
|
+
*/
|
|
202
|
+
export function isValidField(tableName, fieldName) {
|
|
203
|
+
const table = ACTUAL_SCHEMA[tableName];
|
|
204
|
+
if (!table)
|
|
205
|
+
return false;
|
|
206
|
+
return fieldName in table;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if a join path is valid
|
|
210
|
+
*/
|
|
211
|
+
export function isValidJoinPath(path) {
|
|
212
|
+
return path in JOIN_PATHS;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get field type information
|
|
216
|
+
*/
|
|
217
|
+
export function getFieldType(tableName, fieldName) {
|
|
218
|
+
const table = ACTUAL_SCHEMA[tableName];
|
|
219
|
+
if (!table || !(fieldName in table))
|
|
220
|
+
return null;
|
|
221
|
+
return table[fieldName].type;
|
|
222
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget Registry — pre-configured multi-budget, multi-server support.
|
|
3
|
+
*
|
|
4
|
+
* Budgets are declared via environment variables:
|
|
5
|
+
*
|
|
6
|
+
* # Default budget (always present, from existing ACTUAL_* vars)
|
|
7
|
+
* BUDGET_DEFAULT_NAME=My Budget # optional, defaults to "Default"
|
|
8
|
+
* ACTUAL_SERVER_URL=http://actual:5006
|
|
9
|
+
* ACTUAL_PASSWORD=secret
|
|
10
|
+
* ACTUAL_BUDGET_SYNC_ID=uuid-here
|
|
11
|
+
* ACTUAL_BUDGET_PASSWORD= # optional, for E2E-encrypted budgets
|
|
12
|
+
*
|
|
13
|
+
* # Additional named budgets — each group can point to a different server
|
|
14
|
+
* BUDGET_1_NAME=Shared Family Account
|
|
15
|
+
* BUDGET_1_SERVER_URL=http://actual:5006 # optional, falls back to ACTUAL_SERVER_URL
|
|
16
|
+
* BUDGET_1_PASSWORD=secret # optional, falls back to ACTUAL_PASSWORD
|
|
17
|
+
* BUDGET_1_SYNC_ID=uuid-here # required
|
|
18
|
+
* BUDGET_1_ENCRYPTION_PASSWORD= # optional
|
|
19
|
+
*
|
|
20
|
+
* BUDGET_2_NAME=Office
|
|
21
|
+
* BUDGET_2_SERVER_URL=http://actual-office:5006
|
|
22
|
+
* BUDGET_2_PASSWORD=officepassword
|
|
23
|
+
* BUDGET_2_SYNC_ID=uuid-here
|
|
24
|
+
*
|
|
25
|
+
* The AI uses `actual_budgets_list_available` to see all configured budgets,
|
|
26
|
+
* then `actual_budgets_switch` with the budget name to switch between them.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Parse the budget registry from environment variables.
|
|
30
|
+
* Always includes the default budget from the provided defaults (ACTUAL_* vars).
|
|
31
|
+
* Additional budgets are read from sequential BUDGET_n_* groups.
|
|
32
|
+
*/
|
|
33
|
+
export function parseBudgetRegistry(env, defaults) {
|
|
34
|
+
const registry = new Map();
|
|
35
|
+
const defaultName = env.BUDGET_DEFAULT_NAME ?? 'Default';
|
|
36
|
+
registry.set(defaultName.toLowerCase(), {
|
|
37
|
+
name: defaultName,
|
|
38
|
+
serverUrl: defaults.serverUrl,
|
|
39
|
+
password: defaults.password,
|
|
40
|
+
syncId: defaults.syncId,
|
|
41
|
+
encryptionPassword: defaults.encryptionPassword,
|
|
42
|
+
});
|
|
43
|
+
let i = 1;
|
|
44
|
+
while (env[`BUDGET_${i}_NAME`]) {
|
|
45
|
+
const prefix = `BUDGET_${i}_`;
|
|
46
|
+
const name = env[`${prefix}NAME`];
|
|
47
|
+
const serverUrl = env[`${prefix}SERVER_URL`] ?? defaults.serverUrl;
|
|
48
|
+
const password = env[`${prefix}PASSWORD`] ?? defaults.password;
|
|
49
|
+
const syncId = env[`${prefix}SYNC_ID`];
|
|
50
|
+
if (!syncId) {
|
|
51
|
+
console.error(`[CONFIG] BUDGET_${i}_SYNC_ID is required when BUDGET_${i}_NAME="${name}" is set`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
registry.set(name.toLowerCase(), {
|
|
55
|
+
name,
|
|
56
|
+
serverUrl,
|
|
57
|
+
password,
|
|
58
|
+
syncId,
|
|
59
|
+
encryptionPassword: env[`${prefix}ENCRYPTION_PASSWORD`],
|
|
60
|
+
});
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
return registry;
|
|
64
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-wide constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constant values for retry logic, timeouts, limits, and other
|
|
5
|
+
* configuration that should remain consistent across the application.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// RETRY & RESILIENCE
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Default number of retry attempts for transient failures
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_RETRY_ATTEMPTS = 3;
|
|
14
|
+
/**
|
|
15
|
+
* Initial backoff delay in milliseconds for exponential backoff retry
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_RETRY_BACKOFF_MS = 200;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum delay between retries (prevents unbounded exponential growth)
|
|
20
|
+
*/
|
|
21
|
+
export const MAX_RETRY_DELAY_MS = 10000;
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// CONCURRENCY & RATE LIMITING
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Default concurrency limit for Actual Budget API operations
|
|
27
|
+
* Prevents overwhelming the API with too many simultaneous requests
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_CONCURRENCY_LIMIT = 5;
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// TIMEOUTS
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Default timeout for API operations in milliseconds
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_OPERATION_TIMEOUT_MS = 30000;
|
|
37
|
+
/**
|
|
38
|
+
* Timeout for server shutdown grace period
|
|
39
|
+
*/
|
|
40
|
+
export const SHUTDOWN_GRACE_PERIOD_MS = 5000;
|
|
41
|
+
/**
|
|
42
|
+
* How long (ms) to wait after calling rawRunBankSync for the SDK's background
|
|
43
|
+
* promise to surface a BankSyncError as an unhandledRejection.
|
|
44
|
+
*
|
|
45
|
+
* Bank provider errors (GoCardless RATE_LIMIT_EXCEEDED, auth failures, etc.)
|
|
46
|
+
* arrive as HTTP responses. Fast banks respond within 1-3 seconds; slower
|
|
47
|
+
* institutions can take considerably longer. 30 seconds gives a comfortable
|
|
48
|
+
* margin while keeping the tool's wall-clock time acceptable for MCP clients.
|
|
49
|
+
*/
|
|
50
|
+
export const BANK_SYNC_SETTLE_MS = 30_000;
|
|
51
|
+
/**
|
|
52
|
+
* How long (ms) to wait for additional queued writes before closing the
|
|
53
|
+
* shared budget session. Increasing this value batches more writes per
|
|
54
|
+
* session at the cost of slightly higher latency.
|
|
55
|
+
*/
|
|
56
|
+
export const WRITE_SESSION_DELAY_MS = 100;
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// MCP SERVER
|
|
59
|
+
// ============================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Default HTTP server bind host
|
|
62
|
+
*/
|
|
63
|
+
export const DEFAULT_BIND_HOST = 'localhost';
|
|
64
|
+
/**
|
|
65
|
+
* Default HTTP port for MCP server
|
|
66
|
+
*/
|
|
67
|
+
export const DEFAULT_HTTP_PORT = 3000;
|
|
68
|
+
/**
|
|
69
|
+
* Default HTTP path for MCP server endpoint
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_HTTP_PATH = '/';
|
|
72
|
+
/**
|
|
73
|
+
* Server information
|
|
74
|
+
* Note: version is read from package.json at startup and passed as a parameter.
|
|
75
|
+
*/
|
|
76
|
+
export const SERVER_INFO = {
|
|
77
|
+
name: 'actual-budget-mcp',
|
|
78
|
+
description: 'MCP server for Actual Budget - 60 tools for finance management',
|
|
79
|
+
};
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// VALIDATION & LIMITS
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Maximum length for name fields (accounts, categories, payees)
|
|
85
|
+
*/
|
|
86
|
+
export const MAX_NAME_LENGTH = 255;
|
|
87
|
+
/**
|
|
88
|
+
* Maximum length for notes/description fields
|
|
89
|
+
*/
|
|
90
|
+
export const MAX_NOTES_LENGTH = 1000;
|
|
91
|
+
/**
|
|
92
|
+
* Date format pattern (YYYY-MM-DD)
|
|
93
|
+
*/
|
|
94
|
+
export const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
95
|
+
/**
|
|
96
|
+
* Month format pattern (YYYY-MM)
|
|
97
|
+
*/
|
|
98
|
+
export const MONTH_PATTERN = /^\d{4}-\d{2}$/;
|
|
99
|
+
/**
|
|
100
|
+
* UUID pattern for ID validation
|
|
101
|
+
*/
|
|
102
|
+
export const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// LOGGING
|
|
105
|
+
// ============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Log level for production environments
|
|
108
|
+
*/
|
|
109
|
+
export const PRODUCTION_LOG_LEVEL = 'info';
|
|
110
|
+
/**
|
|
111
|
+
* Log level for development environments
|
|
112
|
+
*/
|
|
113
|
+
export const DEVELOPMENT_LOG_LEVEL = 'debug';
|
|
114
|
+
/**
|
|
115
|
+
* Maximum number of log files to retain
|
|
116
|
+
*/
|
|
117
|
+
export const MAX_LOG_FILES = 14;
|
|
118
|
+
/**
|
|
119
|
+
* Maximum size of a single log file before rotation
|
|
120
|
+
*/
|
|
121
|
+
export const MAX_LOG_FILE_SIZE = '20m';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a "not found" error message with a next-step hint.
|
|
3
|
+
* @param entityType Human-readable entity name, e.g. "Account", "Category"
|
|
4
|
+
* @param id The ID that was not found
|
|
5
|
+
* @param listTool MCP tool name the caller should use to get valid IDs
|
|
6
|
+
*/
|
|
7
|
+
export function notFoundMsg(entityType, id, listTool) {
|
|
8
|
+
return `${entityType} "${id}" not found. Use ${listTool} to list available ${entityType.toLowerCase()}s.`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build a "constraint error" message for SQLite-level failures.
|
|
12
|
+
* @param entityType Human-readable entity name
|
|
13
|
+
* @param id The ID that failed
|
|
14
|
+
* @param listTool MCP tool name for listing
|
|
15
|
+
*/
|
|
16
|
+
export function constraintErrorMsg(entityType, id, listTool) {
|
|
17
|
+
return `Failed to delete ${entityType.toLowerCase()} "${id}". ` +
|
|
18
|
+
`It may be referenced by other records. Use ${listTool} to verify it exists.`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Logger Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates module-specific loggers with consistent formatting.
|
|
5
|
+
* Each logger automatically prefixes messages with [MODULE_NAME] for
|
|
6
|
+
* easier tracing and debugging in production.
|
|
7
|
+
*/
|
|
8
|
+
import logger from '../logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a module-specific logger
|
|
11
|
+
*
|
|
12
|
+
* @param moduleName - Name of the module (e.g., 'HTTP', 'ADAPTER', 'TOOLS')
|
|
13
|
+
* @returns Module logger with automatic prefixing
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createModuleLogger } from '../lib/loggerFactory.js';
|
|
18
|
+
*
|
|
19
|
+
* const log = createModuleLogger('HTTP');
|
|
20
|
+
* log.info('Server started', { port: 3000 });
|
|
21
|
+
* // Output: [HTTP] Server started { port: 3000 }
|
|
22
|
+
*
|
|
23
|
+
* log.error('Connection failed', new Error('Timeout'), { retries: 3 });
|
|
24
|
+
* // Output: [HTTP] Connection failed { error: 'Timeout', stack: '...', retries: 3 }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createModuleLogger(moduleName) {
|
|
28
|
+
const prefix = `[${moduleName}]`;
|
|
29
|
+
return {
|
|
30
|
+
info: (message, meta) => {
|
|
31
|
+
logger.info(`${prefix} ${message}`, meta);
|
|
32
|
+
},
|
|
33
|
+
debug: (message, meta) => {
|
|
34
|
+
logger.debug(`${prefix} ${message}`, meta);
|
|
35
|
+
},
|
|
36
|
+
warn: (message, meta) => {
|
|
37
|
+
logger.warn(`${prefix} ${message}`, meta);
|
|
38
|
+
},
|
|
39
|
+
error: (message, error, meta) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
logger.error(`${prefix} ${message}`, {
|
|
42
|
+
error: error.message,
|
|
43
|
+
stack: error.stack,
|
|
44
|
+
...meta,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
logger.error(`${prefix} ${message}`, meta);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Pre-configured module loggers for common components
|
|
55
|
+
* Can be imported directly for convenience
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { ModuleLoggers } from '../lib/loggerFactory.js';
|
|
60
|
+
*
|
|
61
|
+
* ModuleLoggers.HTTP.info('Request received');
|
|
62
|
+
* ModuleLoggers.ADAPTER.debug('Processing transaction');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const ModuleLoggers = {
|
|
66
|
+
HTTP: createModuleLogger('HTTP'),
|
|
67
|
+
ADAPTER: createModuleLogger('ADAPTER'),
|
|
68
|
+
TOOLS: createModuleLogger('TOOLS'),
|
|
69
|
+
SESSION: createModuleLogger('SESSION'),
|
|
70
|
+
CONNECTION: createModuleLogger('CONNECTION'),
|
|
71
|
+
RETRY: createModuleLogger('RETRY'),
|
|
72
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js polyfills for browser globals required by @actual-app/api.
|
|
3
|
+
*
|
|
4
|
+
* @actual-app/api v26.3.0 introduced `navigator.platform` usage at the module
|
|
5
|
+
* top level (inside the Electron/browser bundle) which crashes on Node.js with
|
|
6
|
+
* `ReferenceError: navigator is not defined`.
|
|
7
|
+
*
|
|
8
|
+
* This file must be imported BEFORE any `@actual-app/api` import so that the
|
|
9
|
+
* global is defined when the bundle is first evaluated.
|
|
10
|
+
*/
|
|
11
|
+
if (typeof globalThis.navigator === 'undefined') {
|
|
12
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
13
|
+
value: {
|
|
14
|
+
platform: process.platform === 'win32' ? 'Win32' : 'Linux',
|
|
15
|
+
},
|
|
16
|
+
writable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export {};
|