actual-mcp-server 0.6.9 → 0.6.11
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 +2 -2
- package/dist/package.json +2 -2
- package/dist/src/index.js +3 -35
- package/dist/src/lib/rejection-allowlist.js +55 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Actual MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/
|
|
|
33
33
|
|
|
34
34
|
Most Actual Budget MCP implementations are simple stdio bridges designed for single-user, local use with Claude Desktop. This project goes further:
|
|
35
35
|
|
|
36
|
-
- **
|
|
36
|
+
- **63 tools, the most comprehensive coverage available.** Accounts, transactions, categories, payees, rules, budgets, batch operations, bank sync, and more. Covers 84% of the Actual Budget API.
|
|
37
37
|
- **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.
|
|
38
38
|
- **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.
|
|
39
39
|
- **Multi-budget switching at runtime.** Configure multiple budget files and let the AI switch between them mid-conversation with `actual_budgets_switch`.
|
|
@@ -730,4 +730,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
730
730
|
|
|
731
731
|
---
|
|
732
732
|
|
|
733
|
-
**Version:** 0.6.
|
|
733
|
+
**Version:** 0.6.11 | **Tool Count:** 63 (verified LibreChat-compatible)
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.11",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -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/generated_tools.smoke.test.js && node tests/unit/schema_validation.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_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.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/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/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.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_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.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/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/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.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",
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { isKnownBenignRejection } from './lib/rejection-allowlist.js';
|
|
2
3
|
// Add global error handlers
|
|
3
4
|
let isHandlingQueryError = false;
|
|
4
5
|
process.on('unhandledRejection', (reason, promise) => {
|
|
@@ -9,45 +10,12 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
9
10
|
console.error('Stack:', reason.stack);
|
|
10
11
|
}
|
|
11
12
|
console.error('===========================');
|
|
12
|
-
|
|
13
|
-
// These indicate invalid user input, not server bugs — the error is properly
|
|
14
|
-
// returned to the caller; if it somehow escapes as an unhandled rejection
|
|
15
|
-
// we should log but NOT crash the server.
|
|
16
|
-
const reasonStr = String(reason);
|
|
17
|
-
const reasonObj = reason;
|
|
18
|
-
if (reasonStr.includes('does not exist in table') ||
|
|
19
|
-
(reasonStr.includes('Field') && reasonStr.includes('does not exist')) ||
|
|
20
|
-
reasonStr.includes('Expression stack') ||
|
|
21
|
-
reasonStr.includes('Date is required') ||
|
|
22
|
-
reasonStr.includes('date condition is required') ||
|
|
23
|
-
reasonStr.includes('Cannot create schedules with the same name') ||
|
|
24
|
-
reasonStr.includes('Schedule') && reasonStr.includes('not found') ||
|
|
25
|
-
reasonStr.includes('is system-managed and not user-editable') ||
|
|
26
|
-
reasonStr.includes('is not an expense category') ||
|
|
27
|
-
// Bank sync errors from GoCardless/SimpleFIN/Nordigen surface as unhandled
|
|
28
|
-
// rejections from within the @actual-app/api SDK worker. These are non-fatal:
|
|
29
|
-
// the caller already received a proper error response (or the retry will).
|
|
30
|
-
reasonObj?.type === 'BankSyncError' ||
|
|
31
|
-
reasonStr.includes('BankSyncError') ||
|
|
32
|
-
reasonStr.includes('NORDIGEN_ERROR') ||
|
|
33
|
-
reasonStr.includes('RATE_LIMIT_EXCEEDED') ||
|
|
34
|
-
reasonStr.includes('Rate limit exceeded') ||
|
|
35
|
-
reasonStr.includes('Failed syncing account') ||
|
|
36
|
-
reasonStr.includes('GoCardless') ||
|
|
37
|
-
reasonStr.includes('SimpleFIN') ||
|
|
38
|
-
// Actual API auth failures (network-failure, too-many-requests, invalid-password,
|
|
39
|
-
// etc.) can escape as unhandled rejections from session-init code paths that
|
|
40
|
-
// create a deferred Promise but only conditionally await it (see #132). The
|
|
41
|
-
// primary fix lives in httpServer.ts (.catch on initPromise); this allow-list
|
|
42
|
-
// entry is defence-in-depth so any future deferred-promise leak in the same
|
|
43
|
-
// family also fails non-fatally.
|
|
44
|
-
reasonStr.includes('Authentication failed:')) {
|
|
13
|
+
if (isKnownBenignRejection(reason)) {
|
|
45
14
|
console.error('⚠️ Known Actual API domain error escaped to unhandledRejection:');
|
|
46
|
-
console.error('⚠️ ' +
|
|
15
|
+
console.error('⚠️ ' + String(reason));
|
|
47
16
|
console.error('⚠️ Server will continue running. The caller received an error response.');
|
|
48
17
|
return;
|
|
49
18
|
}
|
|
50
|
-
// For all other unhandled rejections, exit
|
|
51
19
|
process.exit(1);
|
|
52
20
|
});
|
|
53
21
|
process.on('uncaughtException', (error) => {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predicate for the unhandledRejection allow-list in src/index.ts.
|
|
3
|
+
*
|
|
4
|
+
* Returns true when a rejection should be logged but the server should keep
|
|
5
|
+
* running. Centralised here so it can be unit-tested without importing the
|
|
6
|
+
* full server entrypoint.
|
|
7
|
+
*/
|
|
8
|
+
export function isKnownBenignRejection(reason) {
|
|
9
|
+
const reasonStr = String(reason);
|
|
10
|
+
const reasonObj = reason;
|
|
11
|
+
return (reasonStr.includes('does not exist in table') ||
|
|
12
|
+
(reasonStr.includes('Field') && reasonStr.includes('does not exist')) ||
|
|
13
|
+
reasonStr.includes('Expression stack') ||
|
|
14
|
+
reasonStr.includes('Date is required') ||
|
|
15
|
+
reasonStr.includes('date condition is required') ||
|
|
16
|
+
reasonStr.includes('Cannot create schedules with the same name') ||
|
|
17
|
+
(reasonStr.includes('Schedule') && reasonStr.includes('not found')) ||
|
|
18
|
+
reasonStr.includes('is system-managed and not user-editable') ||
|
|
19
|
+
reasonStr.includes('is not an expense category') ||
|
|
20
|
+
reasonObj?.type === 'BankSyncError' ||
|
|
21
|
+
reasonStr.includes('BankSyncError') ||
|
|
22
|
+
reasonStr.includes('NORDIGEN_ERROR') ||
|
|
23
|
+
reasonStr.includes('RATE_LIMIT_EXCEEDED') ||
|
|
24
|
+
reasonStr.includes('Rate limit exceeded') ||
|
|
25
|
+
reasonStr.includes('Failed syncing account') ||
|
|
26
|
+
reasonStr.includes('GoCardless') ||
|
|
27
|
+
reasonStr.includes('SimpleFIN') ||
|
|
28
|
+
reasonStr.includes('Authentication failed:') ||
|
|
29
|
+
isActualApiWorkerRejection(reason));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Rejection that escapes from the @actual-app/api worker's internal cleanup
|
|
33
|
+
* path. The known trigger is a non-writable MCP_BRIDGE_DATA_DIR causing an
|
|
34
|
+
* EACCES during budget download, but the same code path emits a secondary
|
|
35
|
+
* rejection for other internal failures too.
|
|
36
|
+
*
|
|
37
|
+
* The secondary rejection is an Error whose only set property is `stack`
|
|
38
|
+
* (no code/errno/syscall, even non-enumerable, on the rejection itself; those
|
|
39
|
+
* are on the PRIMARY error which ActualConnectionPool already catches).
|
|
40
|
+
* Anchoring on the stack alone is therefore the correct signal.
|
|
41
|
+
*
|
|
42
|
+
* Two-anchor disjunction:
|
|
43
|
+
* - 'download-budget': the precise frame for today's known trigger.
|
|
44
|
+
* - '@actual-app/api/dist': the durable path anchor; survives upstream
|
|
45
|
+
* handler renames as long as the package is still loaded from a
|
|
46
|
+
* conventional npm install path.
|
|
47
|
+
*/
|
|
48
|
+
export function isActualApiWorkerRejection(reason) {
|
|
49
|
+
if (!reason || typeof reason !== 'object')
|
|
50
|
+
return false;
|
|
51
|
+
const stack = reason.stack;
|
|
52
|
+
if (typeof stack !== 'string')
|
|
53
|
+
return false;
|
|
54
|
+
return stack.includes('download-budget') || stack.includes('@actual-app/api/dist');
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.11",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -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/generated_tools.smoke.test.js && node tests/unit/schema_validation.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_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.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/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/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.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_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.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/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/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.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",
|