actual-mcp-server 0.17.0 → 0.18.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/README.md +11 -10
- package/dist/src/actualToolsManager.js +1 -0
- package/dist/src/lib/actual-adapter.js +31 -1
- package/dist/src/lib/financial-analysis.js +116 -0
- package/dist/src/lib/tool-annotations.js +1 -0
- package/dist/src/tools/account_flow_summary.js +52 -0
- package/dist/src/tools/index.js +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ Actual MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/
|
|
|
24
24
|
┌─────────────┐ MCP/HTTP ┌──────────────────┐ Actual API ┌──────────────┐
|
|
25
25
|
│ LibreChat │ ◄───────────► │ Actual MCP │ ◄───────────► │ Actual │
|
|
26
26
|
│ LobeChat │ │ Server │ │ Budget │
|
|
27
|
-
│ (remote) │ │ (
|
|
27
|
+
│ (remote) │ │ (76 tools) │ │ Server │
|
|
28
28
|
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
29
29
|
|
|
30
30
|
┌─────────────┐ MCP/stdio ┌──────────────────┐ Actual API ┌──────────────┐
|
|
31
31
|
│ Claude │ ◄───────────► │ Actual MCP │ ◄───────────► │ Actual │
|
|
32
32
|
│ Desktop │ │ Server │ │ Budget │
|
|
33
|
-
│ (local) │ │ (
|
|
33
|
+
│ (local) │ │ (76 tools) │ │ Server │
|
|
34
34
|
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -38,14 +38,14 @@ Actual MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/
|
|
|
38
38
|
|
|
39
39
|
Most Actual Budget MCP implementations are simple stdio bridges designed for single-user, local use with Claude Desktop. This project goes further:
|
|
40
40
|
|
|
41
|
-
- **
|
|
41
|
+
- **76 tools, the most comprehensive coverage available.** Accounts, transactions, categories, payees, tags, notes, rules, budgets, batch operations, bank sync, and more. Covers the reachable Actual Budget API with no genuine gaps.
|
|
42
42
|
- **HTTP and stdio transport.** Runs as a real remote server for LibreChat/LobeChat (`--http`), or as a direct local process for Claude Desktop (`--stdio`). No Docker or HTTP server is needed for local use.
|
|
43
43
|
- **6 exclusive ActualQL-powered tools.** Search and summarise transactions by month, amount, category, or payee using Actual Budget's native query engine. Aggregated results, no raw data dumped into the AI context window.
|
|
44
44
|
- **Multi-budget switching at runtime.** Configure multiple budget files and let the AI switch between them mid-conversation with `actual_budgets_switch`. Works on both transports: HTTP keys the active budget to the MCP session, and stdio (Claude Desktop, Claude Code, Cursor) gets a synthetic per-process session so a switch is scoped to that process rather than shared globally (#348).
|
|
45
45
|
- **Multi-user ready with OIDC.** Secure every session with JWKS-validated JWTs and per-user budget ACLs. No shared tokens required.
|
|
46
46
|
- **Production-grade reliability on both transports.** HTTP connection pooling (up to 15 concurrent sessions), and a long-lived stdio process that logs in ONCE and reuses that connection for every tool call instead of re-authenticating per call, so Claude Desktop and Claude Code stay fast and a burst of calls no longer risks a per-call login storm against the upstream limiter. Automatic retry with exponential backoff, and a full test suite (unit + E2E + integration).
|
|
47
47
|
|
|
48
|
-
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All
|
|
48
|
+
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All 76 tools tested end-to-end. Any MCP-compatible client should work.
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
@@ -178,7 +178,7 @@ Add to `claude_desktop_config.json` (see [docs/guides/MCP_CLIENTS_SETUP.md](docs
|
|
|
178
178
|
}
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
> **No token needed.** stdio runs as a local process owned by your user. The transport itself is the security boundary. All
|
|
181
|
+
> **No token needed.** stdio runs as a local process owned by your user. The transport itself is the security boundary. All 76 tools are available.
|
|
182
182
|
>
|
|
183
183
|
> **`MCP_BRIDGE_DATA_DIR` should be an absolute path.** Without one, the data directory resolves relative to wherever the client spawns the process, which can be unpredictable. The directory is created automatically on first run.
|
|
184
184
|
|
|
@@ -304,9 +304,9 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
304
304
|
|
|
305
305
|
## Available Tools
|
|
306
306
|
|
|
307
|
-
**
|
|
307
|
+
**76 tools** across all categories. All tools use the `actual_<category>_<action>` naming convention.
|
|
308
308
|
|
|
309
|
-
### Accounts (
|
|
309
|
+
### Accounts (8)
|
|
310
310
|
|
|
311
311
|
| Tool | Description |
|
|
312
312
|
|------|-------------|
|
|
@@ -317,6 +317,7 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
317
317
|
| `actual_accounts_close` | Close account. An account with NO transactions is REMOVED by Actual, not closed |
|
|
318
318
|
| `actual_accounts_reopen` | Reopen closed account. An id that is not an account is refused rather than creating one |
|
|
319
319
|
| `actual_accounts_get_balance` | Get account balance at a date |
|
|
320
|
+
| `actual_account_flow_summary` | Explain the exact balance change across a set of accounts over a date range: external income, expense outflow, credits, uncategorized inflows, and transfers into, out of, or within the selection, with an exact reconciliation |
|
|
320
321
|
|
|
321
322
|
### Transactions (15)
|
|
322
323
|
|
|
@@ -592,7 +593,7 @@ stdio is the simplest way to connect Claude Desktop directly to Actual Budget. T
|
|
|
592
593
|
- No auth token. Process ownership is the security boundary.
|
|
593
594
|
- All logs go to stderr so they never corrupt the JSON-RPC framing on stdout
|
|
594
595
|
- The process exits when stdin closes (Claude Desktop shutting down)
|
|
595
|
-
- All
|
|
596
|
+
- All 76 tools are available, identical to HTTP mode
|
|
596
597
|
|
|
597
598
|
**Start manually to verify:**
|
|
598
599
|
|
|
@@ -691,7 +692,7 @@ See [AI Client Setup, OIDC](docs/guides/AI_CLIENT_SETUP.md#oidc-authentication-m
|
|
|
691
692
|
| Command | What It Tests | Requires Live Server |
|
|
692
693
|
|---------|---------------|---------------------|
|
|
693
694
|
| `npm run build` | TypeScript compilation | No |
|
|
694
|
-
| `npm run test:unit-js` |
|
|
695
|
+
| `npm run test:unit-js` | 76-tool smoke, schema validation, auth ACL | No |
|
|
695
696
|
| `npm run test:adapter` | Adapter, retry logic, concurrency | No |
|
|
696
697
|
| `npm run test:e2e` | MCP protocol compliance (Playwright) | No |
|
|
697
698
|
| `npm run test:e2e:docker:full` | Full stack integration | Yes (Docker) |
|
|
@@ -776,7 +777,7 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
776
777
|
|
|
777
778
|
---
|
|
778
779
|
|
|
779
|
-
**Version:** 0.
|
|
780
|
+
**Version:** 0.18.0 | **Tool Count:** 76 (verified LibreChat-compatible)
|
|
780
781
|
|
|
781
782
|
## Sponsor
|
|
782
783
|
|
|
@@ -4,6 +4,7 @@ import { formatZodError } from './lib/zod-error-format.js';
|
|
|
4
4
|
// ✅ List of tools already implemented in this class.
|
|
5
5
|
// Adding the tool name here is considered fully implemented.
|
|
6
6
|
const IMPLEMENTED_TOOLS = [
|
|
7
|
+
'actual_account_flow_summary',
|
|
7
8
|
'actual_accounts_close',
|
|
8
9
|
'actual_accounts_create',
|
|
9
10
|
'actual_accounts_delete',
|
|
@@ -2849,7 +2849,37 @@ export async function getFinancialAnalysisSnapshot(params) {
|
|
|
2849
2849
|
const categories = await queryData(q('categories').select(['id', 'name', 'is_income', 'group']));
|
|
2850
2850
|
const categoryGroups = await queryData(q('category_groups').options({ categories: 'none' }).select(['id', 'name', 'is_income']));
|
|
2851
2851
|
const payees = await queryData(q('payees').select(['id', 'name', 'transfer_acct']));
|
|
2852
|
-
|
|
2852
|
+
// #425: the transfer counterparts (the OTHER leg of each transfer), so account-flow can name the
|
|
2853
|
+
// funding account. splits:'all' here (not inline/grouped) so a split transfer leg is still seen.
|
|
2854
|
+
const transferIds = [...new Set(transactions.flatMap(t => [t, ...(t.subtransactions ?? [])])
|
|
2855
|
+
.map(t => t.transfer_id)
|
|
2856
|
+
.filter((id) => typeof id === 'string' && id.length > 0))];
|
|
2857
|
+
const transferCounterparts = transferIds.length > 0
|
|
2858
|
+
? await queryData(q('transactions').options({ splits: 'all' }).filter({ id: { $oneof: transferIds } }).select('*'))
|
|
2859
|
+
: [];
|
|
2860
|
+
// #425: opening (strictly before startDate) and closing (through endDate) balances for the
|
|
2861
|
+
// selected accounts. splits:'inline' expands a split to its children so each posting is summed
|
|
2862
|
+
// ONCE; splits:'all' would double-count a split parent plus its children and break reconciliation.
|
|
2863
|
+
const openingBalances = {};
|
|
2864
|
+
const closingBalances = {};
|
|
2865
|
+
const balanceAccountIds = params.balanceAccountIds ?? [];
|
|
2866
|
+
if (balanceAccountIds.length > 0) {
|
|
2867
|
+
const openingRows = await queryData(q('transactions').options({ splits: 'inline' })
|
|
2868
|
+
.filter({ $and: [{ account: { $oneof: balanceAccountIds } }, { date: { $lt: params.startDate } }] })
|
|
2869
|
+
.groupBy('account').select(['account', { balance: { $sum: '$amount' } }]));
|
|
2870
|
+
const closingRows = await queryData(q('transactions').options({ splits: 'inline' })
|
|
2871
|
+
.filter({ $and: [{ account: { $oneof: balanceAccountIds } }, { date: { $lte: params.endDate } }] })
|
|
2872
|
+
.groupBy('account').select(['account', { balance: { $sum: '$amount' } }]));
|
|
2873
|
+
for (const id of balanceAccountIds) {
|
|
2874
|
+
openingBalances[id] = 0;
|
|
2875
|
+
closingBalances[id] = 0;
|
|
2876
|
+
}
|
|
2877
|
+
for (const row of openingRows)
|
|
2878
|
+
openingBalances[row.account] = row.balance ?? 0;
|
|
2879
|
+
for (const row of closingRows)
|
|
2880
|
+
closingBalances[row.account] = row.balance ?? 0;
|
|
2881
|
+
}
|
|
2882
|
+
return { transactions, transferCounterparts, accounts, categories, categoryGroups, payees, openingBalances, closingBalances };
|
|
2853
2883
|
});
|
|
2854
2884
|
}
|
|
2855
2885
|
// WHERE-clause translation extracted to ./actual-adapter/query.ts (#166).
|
|
@@ -212,3 +212,119 @@ export function aggregateTransactions(snapshot, input) {
|
|
|
212
212
|
addAggregate(totals, entry.values);
|
|
213
213
|
return { dateRange: { startDate: input.startDate, endDate: input.endDate }, groups, totals };
|
|
214
214
|
}
|
|
215
|
+
export function summarizeAccountFlow(snapshot, input) {
|
|
216
|
+
const maps = buildMaps(snapshot);
|
|
217
|
+
const selected = new Set(input.accountIds);
|
|
218
|
+
// Dedupe once. The balance reduces and the accounts list below MUST key off the same unique set
|
|
219
|
+
// the rows are filtered against, or a repeated id (two display names resolving to one account, say)
|
|
220
|
+
// doubles the balances while the Set counts postings once, making reconciliation.difference non-zero
|
|
221
|
+
// and falsifying the tool's exact-by-construction guarantee.
|
|
222
|
+
const uniqueAccountIds = [...selected];
|
|
223
|
+
const rows = postingRows(snapshot.transactions).filter(transaction => selected.has(transaction.account) && transaction.date >= input.startDate && transaction.date <= input.endDate);
|
|
224
|
+
let income = 0;
|
|
225
|
+
let incomeReversals = 0;
|
|
226
|
+
let expenseOutflow = 0;
|
|
227
|
+
let credits = 0;
|
|
228
|
+
let uncategorizedInflows = 0;
|
|
229
|
+
let startingBalance = 0;
|
|
230
|
+
let intoSelection = 0;
|
|
231
|
+
let outOfSelection = 0;
|
|
232
|
+
let withinSelection = 0;
|
|
233
|
+
let withinSelectionTimingEffect = 0;
|
|
234
|
+
let netTransferEffect = 0;
|
|
235
|
+
const seenPairs = new Set();
|
|
236
|
+
const transfersByAccount = [];
|
|
237
|
+
for (const transaction of rows) {
|
|
238
|
+
const amount = integer(transaction.amount, `Transaction ${transaction.id} amount`);
|
|
239
|
+
const category = transaction.category ? maps.categories.get(transaction.category) : undefined;
|
|
240
|
+
const classification = classifyExternal(transaction, category);
|
|
241
|
+
if (classification === 'expense')
|
|
242
|
+
expenseOutflow += Math.abs(amount);
|
|
243
|
+
if (classification === 'credit')
|
|
244
|
+
credits += amount;
|
|
245
|
+
if (classification === 'income') {
|
|
246
|
+
if (amount >= 0)
|
|
247
|
+
income += amount;
|
|
248
|
+
else
|
|
249
|
+
incomeReversals += Math.abs(amount);
|
|
250
|
+
}
|
|
251
|
+
if (classification === 'uncategorized-inflow')
|
|
252
|
+
uncategorizedInflows += amount;
|
|
253
|
+
if (classification === 'adjustment')
|
|
254
|
+
startingBalance += amount;
|
|
255
|
+
if (classification !== 'transfer')
|
|
256
|
+
continue;
|
|
257
|
+
netTransferEffect += amount;
|
|
258
|
+
const counterpart = transaction.transfer_id ? maps.transactions.get(transaction.transfer_id) : undefined;
|
|
259
|
+
const payee = transaction.payee ? maps.payees.get(transaction.payee) : undefined;
|
|
260
|
+
const counterpartAccountId = counterpart?.account ?? payee?.transfer_acct ?? null;
|
|
261
|
+
const counterpartSelected = counterpartAccountId ? selected.has(counterpartAccountId) : false;
|
|
262
|
+
if (counterpartSelected) {
|
|
263
|
+
withinSelectionTimingEffect += amount;
|
|
264
|
+
const pairKey = [transaction.id, transaction.transfer_id ?? transaction.id].sort().join(':');
|
|
265
|
+
if (!seenPairs.has(pairKey)) {
|
|
266
|
+
seenPairs.add(pairKey);
|
|
267
|
+
withinSelection += Math.abs(amount);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (amount >= 0) {
|
|
271
|
+
intoSelection += amount;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
outOfSelection += Math.abs(amount);
|
|
275
|
+
}
|
|
276
|
+
const pairKey = [transaction.id, transaction.transfer_id ?? transaction.id].sort().join(':');
|
|
277
|
+
if (!seenPairs.has(`detail:${pairKey}`)) {
|
|
278
|
+
seenPairs.add(`detail:${pairKey}`);
|
|
279
|
+
const fromId = amount < 0 ? transaction.account : counterpartAccountId;
|
|
280
|
+
const toId = amount < 0 ? counterpartAccountId : transaction.account;
|
|
281
|
+
transfersByAccount.push({
|
|
282
|
+
fromAccountId: fromId,
|
|
283
|
+
fromAccountName: fromId ? maps.accounts.get(fromId)?.name ?? fromId : null,
|
|
284
|
+
toAccountId: toId,
|
|
285
|
+
toAccountName: toId ? maps.accounts.get(toId)?.name ?? toId : null,
|
|
286
|
+
amount: Math.abs(amount),
|
|
287
|
+
date: transaction.date,
|
|
288
|
+
withinSelection: counterpartSelected,
|
|
289
|
+
matchedCounterpart: counterpart != null,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// The tool handler always supplies balances (via balanceAccountIds), so difference is exact here.
|
|
294
|
+
// If a future caller omits them, both reduce to 0 and difference reads as a false "exact": #428.
|
|
295
|
+
const openingBalance = uniqueAccountIds.reduce((sum, id) => sum + integer(snapshot.openingBalances?.[id] ?? 0, `Opening balance for ${id}`), 0);
|
|
296
|
+
const closingBalance = uniqueAccountIds.reduce((sum, id) => sum + integer(snapshot.closingBalances?.[id] ?? 0, `Closing balance for ${id}`), 0);
|
|
297
|
+
const balanceChange = closingBalance - openingBalance;
|
|
298
|
+
const netExternalCashFlow = income - incomeReversals + credits + uncategorizedInflows - expenseOutflow;
|
|
299
|
+
const totalAdjustments = startingBalance;
|
|
300
|
+
const calculatedBalanceChange = netExternalCashFlow + netTransferEffect + totalAdjustments;
|
|
301
|
+
return {
|
|
302
|
+
dateRange: { startDate: input.startDate, endDate: input.endDate },
|
|
303
|
+
accounts: uniqueAccountIds.map(id => ({ id, name: maps.accounts.get(id)?.name ?? id })),
|
|
304
|
+
openingBalance,
|
|
305
|
+
closingBalance,
|
|
306
|
+
balanceChange,
|
|
307
|
+
external: {
|
|
308
|
+
income,
|
|
309
|
+
incomeReversals,
|
|
310
|
+
expenseOutflow,
|
|
311
|
+
credits,
|
|
312
|
+
uncategorizedInflows,
|
|
313
|
+
netExternalCashFlow,
|
|
314
|
+
},
|
|
315
|
+
transfers: {
|
|
316
|
+
intoSelection,
|
|
317
|
+
outOfSelection,
|
|
318
|
+
withinSelection,
|
|
319
|
+
withinSelectionTimingEffect,
|
|
320
|
+
netTransferEffect,
|
|
321
|
+
},
|
|
322
|
+
transfersByAccount: transfersByAccount.sort((a, b) => `${a.date}:${a.fromAccountId}:${a.toAccountId}`.localeCompare(`${b.date}:${b.fromAccountId}:${b.toAccountId}`)),
|
|
323
|
+
adjustments: { startingBalance, total: totalAdjustments },
|
|
324
|
+
reconciliation: {
|
|
325
|
+
calculatedBalanceChange,
|
|
326
|
+
actualBalanceChange: balanceChange,
|
|
327
|
+
difference: balanceChange - calculatedBalanceChange,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -55,6 +55,7 @@ const OPEN_WORLD = new Set(['actual_bank_sync']);
|
|
|
55
55
|
* actual_session_close closes a pooled connection: server state, not budget data.
|
|
56
56
|
*/
|
|
57
57
|
const READ_ONLY = new Set([
|
|
58
|
+
'actual_account_flow_summary',
|
|
58
59
|
'actual_accounts_get_balance',
|
|
59
60
|
'actual_accounts_list',
|
|
60
61
|
'actual_budgets_get_all',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// #425: deterministic, read-only account-flow reconciliation.
|
|
2
|
+
//
|
|
3
|
+
// Original implementation by @maxvanweenen (PR #399), the "deferred secondary" tool from #398. The
|
|
4
|
+
// accounting lives in src/lib/financial-analysis.ts (summarizeAccountFlow); this file is the tool
|
|
5
|
+
// surface plus the #388 filter-id resolution.
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { createTool } from '../lib/toolFactory.js';
|
|
8
|
+
import { CommonSchemas } from '../lib/schemas/common.js';
|
|
9
|
+
import adapter from '../lib/actual-adapter.js';
|
|
10
|
+
import { summarizeAccountFlow } from '../lib/financial-analysis.js';
|
|
11
|
+
// Bare-string ids (#388): a NAME resolves via adapter.resolveFilterId rather than being rejected.
|
|
12
|
+
const InputSchema = z.object({
|
|
13
|
+
startDate: CommonSchemas.date,
|
|
14
|
+
endDate: CommonSchemas.date,
|
|
15
|
+
accountIds: z.array(z.string().min(1)).min(1, 'Select at least one account').max(100),
|
|
16
|
+
}).strict().superRefine((input, ctx) => {
|
|
17
|
+
if (input.startDate > input.endDate) {
|
|
18
|
+
ctx.addIssue({ code: 'custom', path: ['endDate'], message: 'endDate must be on or after startDate' });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
export default createTool({
|
|
22
|
+
name: 'actual_account_flow_summary',
|
|
23
|
+
description: [
|
|
24
|
+
'Explain the exact balance change across a selected set of accounts over an inclusive date range, in integer cents.',
|
|
25
|
+
'Separates external income, expense outflow, credits, uncategorized inflows, starting-balance adjustments, and transfers into, out of, or within the selection.',
|
|
26
|
+
'Transfers are detected from transfer_id and are never spending or income.',
|
|
27
|
+
'openingBalance is immediately before startDate and closingBalance is at the end of the inclusive endDate; reconciliation.difference is exact and transfersByAccount names the funding counterparties.',
|
|
28
|
+
'Amounts are in cents: $100.00 = 10000. Dates are YYYY-MM-DD.',
|
|
29
|
+
].join(' '),
|
|
30
|
+
schema: InputSchema,
|
|
31
|
+
handler: async (input) => {
|
|
32
|
+
// Fetch with the raw ids so the balance queries run in the same session. A NAME here filters the
|
|
33
|
+
// balance query to nothing (0), which is harmless because the resolveFilterId pass below refuses
|
|
34
|
+
// the name before the result is used.
|
|
35
|
+
const snapshot = await adapter.getFinancialAnalysisSnapshot({
|
|
36
|
+
startDate: input.startDate,
|
|
37
|
+
endDate: input.endDate,
|
|
38
|
+
balanceAccountIds: input.accountIds,
|
|
39
|
+
});
|
|
40
|
+
const accountIds = [];
|
|
41
|
+
for (const id of input.accountIds) {
|
|
42
|
+
accountIds.push(await adapter.resolveFilterId('account', id, { verifyExists: true, rows: snapshot.accounts }));
|
|
43
|
+
}
|
|
44
|
+
return summarizeAccountFlow(snapshot, { startDate: input.startDate, endDate: input.endDate, accountIds });
|
|
45
|
+
},
|
|
46
|
+
examples: [
|
|
47
|
+
{
|
|
48
|
+
description: 'Reconcile the flow across two accounts for a month',
|
|
49
|
+
input: { startDate: '2025-01-01', endDate: '2025-01-31', accountIds: ['<account-uuid-1>', '<account-uuid-2>'] },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
package/dist/src/tools/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Auto-generated index for all tool modules
|
|
2
2
|
export { default as entities_search } from './entities_search.js';
|
|
3
|
+
export { default as account_flow_summary } from './account_flow_summary.js';
|
|
3
4
|
export { default as accounts_close } from './accounts_close.js';
|
|
4
5
|
export { default as accounts_create } from './accounts_create.js';
|
|
5
6
|
export { default as accounts_delete } from './accounts_delete.js';
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
8
8
|
},
|
|
9
|
-
"description": "MCP server with
|
|
9
|
+
"description": "MCP server with 76 tools for AI-driven financial management with Actual Budget. HTTP and stdio transports for LibreChat, Claude Desktop, Cursor, VS Code, Gemini CLI",
|
|
10
10
|
"homepage": "https://github.com/agigante80/actual-mcp-server#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
31
31
|
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
32
32
|
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
33
|
-
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/account_flow_summary.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js",
|
|
34
34
|
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
35
35
|
"test:e2e": "npx playwright test",
|
|
36
36
|
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|