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,34 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import adapter from '../lib/actual-adapter.js';
|
|
3
|
+
const InputSchema = z.object({
|
|
4
|
+
id: z.string().optional().describe('Transaction ID to update (optional for smoke tests, required for actual usage)'),
|
|
5
|
+
fields: z.object({
|
|
6
|
+
account: z.string().nullable().optional().describe('Account ID'),
|
|
7
|
+
date: z.string().nullable().optional().describe('Transaction date (YYYY-MM-DD)'),
|
|
8
|
+
amount: z.number().nullable().optional().describe('Amount in cents (e.g., 1000 = $10.00)'),
|
|
9
|
+
payee: z.string().nullable().optional().describe('Payee ID or name'),
|
|
10
|
+
payee_name: z.string().nullable().optional().describe('Payee name (alternative to payee ID)'),
|
|
11
|
+
imported_payee: z.string().nullable().optional().describe('Original imported payee name'),
|
|
12
|
+
category: z.string().nullable().optional().describe('Category ID'),
|
|
13
|
+
notes: z.string().nullable().optional().describe('Transaction notes'),
|
|
14
|
+
imported_id: z.string().nullable().optional().describe('Original imported transaction ID'),
|
|
15
|
+
transfer_id: z.string().nullable().optional().describe('Transfer transaction ID if this is a transfer'),
|
|
16
|
+
cleared: z.boolean().nullable().optional().describe('Whether transaction is cleared'),
|
|
17
|
+
reconciled: z.boolean().nullable().optional().describe('Whether transaction is reconciled'),
|
|
18
|
+
}).describe('Fields to update'),
|
|
19
|
+
});
|
|
20
|
+
const tool = {
|
|
21
|
+
name: 'actual_transactions_update',
|
|
22
|
+
description: 'Update an existing transaction in Actual Budget. Provide the transaction ID and the fields you want to update.',
|
|
23
|
+
inputSchema: InputSchema,
|
|
24
|
+
call: async (args, _meta) => {
|
|
25
|
+
const input = InputSchema.parse(args || {});
|
|
26
|
+
// For smoke tests, return early if no id provided
|
|
27
|
+
if (!input.id) {
|
|
28
|
+
return { success: true };
|
|
29
|
+
}
|
|
30
|
+
await adapter.updateTransaction(input.id, input.fields);
|
|
31
|
+
return { success: true };
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export default tool;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* actual_transactions_update_batch
|
|
3
|
+
*
|
|
4
|
+
* Batch-update multiple transactions in one call.
|
|
5
|
+
*
|
|
6
|
+
* Concept and implementation adapted from the ZanzyTHEbar fork:
|
|
7
|
+
* https://github.com/ZanzyTHEbar/actual-mcp-server/blob/main/src/tools/transactions_update_batch.ts
|
|
8
|
+
* Credit: ZanzyTHEbar (https://github.com/ZanzyTHEbar)
|
|
9
|
+
*
|
|
10
|
+
* Adapted for this project's conventions:
|
|
11
|
+
* - No wrapToolCall — uses direct call() pattern
|
|
12
|
+
* - Returns a plain BatchResult object (no { result } wrapper)
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import adapter from '../lib/actual-adapter.js';
|
|
16
|
+
const FieldsSchema = z.object({
|
|
17
|
+
account: z.string().nullable().optional().describe('Account ID'),
|
|
18
|
+
date: z.string().nullable().optional().describe('Transaction date (YYYY-MM-DD)'),
|
|
19
|
+
amount: z.number().nullable().optional().describe('Amount in cents (e.g., -1000 = -$10.00)'),
|
|
20
|
+
payee: z.string().nullable().optional().describe('Payee ID'),
|
|
21
|
+
payee_name: z.string().nullable().optional().describe('Payee name (alternative to payee ID)'),
|
|
22
|
+
imported_payee: z.string().nullable().optional().describe('Original imported payee name'),
|
|
23
|
+
category: z.string().nullable().optional().describe('Category ID'),
|
|
24
|
+
notes: z.string().nullable().optional().describe('Transaction notes'),
|
|
25
|
+
cleared: z.boolean().nullable().optional().describe('Whether transaction is cleared'),
|
|
26
|
+
});
|
|
27
|
+
const UpdateItemSchema = z.object({
|
|
28
|
+
id: z.string().describe('Transaction ID to update'),
|
|
29
|
+
fields: FieldsSchema.describe('Fields to update for this transaction'),
|
|
30
|
+
});
|
|
31
|
+
const InputSchema = z.object({
|
|
32
|
+
updates: z.array(UpdateItemSchema)
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(100)
|
|
35
|
+
.describe('Array of {id, fields} objects. Maximum 100 per batch.'),
|
|
36
|
+
});
|
|
37
|
+
const tool = {
|
|
38
|
+
name: 'actual_transactions_update_batch',
|
|
39
|
+
description: `Update multiple transactions in a single call. Accepts up to 100 {id, fields} pairs. Each update is applied independently — partial failures are reported per-item so you know exactly which succeeded and which failed.
|
|
40
|
+
|
|
41
|
+
Returns: { succeeded: [{id}], failed: [{id, error}], total, successCount, failureCount }
|
|
42
|
+
|
|
43
|
+
Example: { updates: [{ id: "txn-uuid-1", fields: { category: "cat-uuid" } }, { id: "txn-uuid-2", fields: { notes: "Reimbursement" } }] }`,
|
|
44
|
+
inputSchema: InputSchema,
|
|
45
|
+
call: async (args, _meta) => {
|
|
46
|
+
const input = InputSchema.parse(args || {});
|
|
47
|
+
// Single adapter call — all updates share one init/sync/shutdown cycle (fixes issue #79).
|
|
48
|
+
// Calling adapter.updateTransaction() in a loop would trigger N separate budget sessions.
|
|
49
|
+
const { succeeded, failed } = await adapter.updateTransactionBatch(input.updates);
|
|
50
|
+
const result = {
|
|
51
|
+
succeeded,
|
|
52
|
+
failed,
|
|
53
|
+
total: input.updates.length,
|
|
54
|
+
successCount: succeeded.length,
|
|
55
|
+
failureCount: failed.length,
|
|
56
|
+
};
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
export default tool;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
export function getLocalIp() {
|
|
3
|
+
const interfaces = os.networkInterfaces();
|
|
4
|
+
for (const name of Object.keys(interfaces)) {
|
|
5
|
+
for (const iface of interfaces[name]) {
|
|
6
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
7
|
+
return iface.address;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return '127.0.0.1';
|
|
12
|
+
}
|
|
13
|
+
// new: transport logging helpers (enabled when --debug or DEBUG/MCP_BRIDGE_DEBUG_TRANSPORT set)
|
|
14
|
+
export function shouldDebugTransport() {
|
|
15
|
+
const argvDebug = process.argv.slice(2).includes('--debug');
|
|
16
|
+
const envDebug = Boolean(process.env.DEBUG) || process.env.MCP_BRIDGE_DEBUG_TRANSPORT === 'true' || process.env.LOG_LEVEL === 'debug';
|
|
17
|
+
return argvDebug || envDebug;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Log transport-level request/response objects when debug is enabled.
|
|
21
|
+
* - prefix: short label like "HTTP REQ" / "HTTP RES"
|
|
22
|
+
* - obj: any serializable data (headers, body, meta)
|
|
23
|
+
*
|
|
24
|
+
* This uses console.debug (falls through to console if no structured logger available).
|
|
25
|
+
*/
|
|
26
|
+
export function logTransport(prefix, obj) {
|
|
27
|
+
if (!shouldDebugTransport())
|
|
28
|
+
return;
|
|
29
|
+
const ts = new Date().toISOString();
|
|
30
|
+
let payload;
|
|
31
|
+
try {
|
|
32
|
+
payload = JSON.stringify(obj, replacer, 2);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
try {
|
|
36
|
+
payload = String(obj);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
payload = '[unserializable payload]';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// prefer console.debug so existing debug tooling (DEBUG env) can capture it
|
|
43
|
+
// format similar to other logs: "<iso> debug: [TRANSPORT] prefix: <json>"
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.debug(`${ts} debug: [TRANSPORT] ${prefix}: ${payload}`);
|
|
46
|
+
}
|
|
47
|
+
// Helper replacer to avoid throwing on circular refs and to truncate very large buffers
|
|
48
|
+
function replacer(_key, value) {
|
|
49
|
+
// Limit Buffer/Uint8Array printing
|
|
50
|
+
if (value instanceof Uint8Array || (typeof Buffer !== 'undefined' && value instanceof Buffer)) {
|
|
51
|
+
const buf = value;
|
|
52
|
+
const len = buf.length ?? 0;
|
|
53
|
+
const preview = Array.prototype.slice.call(buf, 0, 64);
|
|
54
|
+
return { _type: 'Buffer', length: len, preview };
|
|
55
|
+
}
|
|
56
|
+
if (value instanceof Uint8Array) {
|
|
57
|
+
const buf = value;
|
|
58
|
+
const len = buf.length;
|
|
59
|
+
const preview = Array.prototype.slice.call(buf, 0, 64);
|
|
60
|
+
return { _type: 'Uint8Array', length: len, preview };
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "actual-mcp-server",
|
|
3
|
+
"displayName": "Actual MCP Server",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"description": "MCP server with 62 tools for AI-driven financial management with Actual Budget — HTTP and stdio transports, LibreChat, Claude Desktop, Cursor, VS Code, Gemini CLI",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"actual-mcp-server": "./bin/actual-mcp-server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "npm run build && node scripts/register-tsconfig-paths.js -- --debug",
|
|
20
|
+
"start": "node dist/src/index.js",
|
|
21
|
+
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
22
|
+
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
23
|
+
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
24
|
+
"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/transactions_uncategorized.test.js",
|
|
25
|
+
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
26
|
+
"test:e2e": "npx playwright test",
|
|
27
|
+
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|
|
28
|
+
"test:e2e:docker:smoke": "./tests/e2e/run-docker-e2e.sh smoke",
|
|
29
|
+
"test:e2e:docker:full": "./tests/e2e/run-docker-e2e.sh full",
|
|
30
|
+
"test:all": "npm run test:adapter && npm run test:unit-js && npm run test:e2e:docker:smoke",
|
|
31
|
+
"test:mcp-client": "npm run build && node scripts/register-tsconfig-paths.js -- --http --test-mcp-client",
|
|
32
|
+
"test:integration": "MCP_TEST_LEVEL=sanity node tests/manual/index.js",
|
|
33
|
+
"test:integration:sanity": "MCP_TEST_LEVEL=sanity node tests/manual/index.js",
|
|
34
|
+
"test:integration:smoke": "MCP_TEST_LEVEL=smoke node tests/manual/index.js",
|
|
35
|
+
"test:integration:normal": "MCP_TEST_LEVEL=normal node tests/manual/index.js",
|
|
36
|
+
"test:integration:extended": "MCP_TEST_LEVEL=extended node tests/manual/index.js",
|
|
37
|
+
"test:integration:full": "MCP_TEST_LEVEL=full node tests/manual/index.js",
|
|
38
|
+
"test:integration:cleanup": "MCP_TEST_LEVEL=cleanup node tests/manual/index.js",
|
|
39
|
+
"deploy:full": "bash scripts/deploy-and-test.sh full",
|
|
40
|
+
"deploy:smoke": "bash scripts/deploy-and-test.sh smoke",
|
|
41
|
+
"docs:sync": "node scripts/version-bump.js sync",
|
|
42
|
+
"version:current": "cat VERSION",
|
|
43
|
+
"version:dev": "node scripts/version-dev.js",
|
|
44
|
+
"version:bump": "node scripts/version-bump.js",
|
|
45
|
+
"version:check": "node scripts/version-check.js",
|
|
46
|
+
"release:major": "npm run version:bump -- major",
|
|
47
|
+
"release:minor": "npm run version:bump -- minor",
|
|
48
|
+
"release:patch": "npm run version:bump -- patch"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@actual-app/api": "^26.4.0",
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
53
|
+
"dotenv": "^17.4.1",
|
|
54
|
+
"express": "^5.2.1",
|
|
55
|
+
"mcp-auth": "^0.2.0",
|
|
56
|
+
"winston": "^3.18.3",
|
|
57
|
+
"winston-daily-rotate-file": "^5.0.0",
|
|
58
|
+
"zod": "^4.0.0"
|
|
59
|
+
},
|
|
60
|
+
"overrides": {
|
|
61
|
+
"ajv": "8.18.0",
|
|
62
|
+
"qs": "6.14.2"
|
|
63
|
+
},
|
|
64
|
+
"comments": {
|
|
65
|
+
"security-overrides": "ajv>=8.18.0 (CVE alert #21), qs>=6.14.2 (alert #17)"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@playwright/test": "^1.59.1",
|
|
69
|
+
"@types/express": "^5.0.3",
|
|
70
|
+
"@types/node": "^25.6.0",
|
|
71
|
+
"tsconfig-paths": "^4.2.0",
|
|
72
|
+
"typescript": "^6.0.2"
|
|
73
|
+
},
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"keywords": [
|
|
76
|
+
"mcp",
|
|
77
|
+
"model-context-protocol",
|
|
78
|
+
"actual-budget",
|
|
79
|
+
"actual-finance",
|
|
80
|
+
"librechat",
|
|
81
|
+
"claude-desktop",
|
|
82
|
+
"cursor",
|
|
83
|
+
"budget",
|
|
84
|
+
"finance",
|
|
85
|
+
"ai"
|
|
86
|
+
],
|
|
87
|
+
"author": "agigante80"
|
|
88
|
+
}
|