actual-mcp-server 0.6.41 → 0.6.42

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 CHANGED
@@ -750,4 +750,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
750
750
 
751
751
  ---
752
752
 
753
- **Version:** 0.6.41 | **Tool Count:** 70 (verified LibreChat-compatible)
753
+ **Version:** 0.6.42 | **Tool Count:** 70 (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.41",
4
+ "version": "0.6.42",
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/config_https_validation.test.js && node tests/unit/config_insecure_upstream.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/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/payees_delete.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_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.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/check_coverage.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/config_https_validation.test.js && node tests/unit/config_insecure_upstream.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/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/payees_delete.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_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.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/check_coverage.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",
@@ -1010,6 +1010,18 @@ export async function updateTransaction(id, fields) {
1010
1010
  observability.incrementToolCall('actual.transactions.update').catch(() => { });
1011
1011
  // Use write queue to batch concurrent updates in a single budget session
1012
1012
  return queueWriteOperation(async () => {
1013
+ // Pre-flight existence check (#212): the raw API silently no-ops on a missing id,
1014
+ // so an update that changed nothing would otherwise be reported as success. A
1015
+ // targeted ActualQL query by id keeps this cheap (indexed lookup).
1016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1017
+ const { q } = (await import('@actual-app/api'));
1018
+ const found = await withConcurrency(() => retry(async () => {
1019
+ const res = (await rawRunQuery(q('transactions').filter({ id }).select(['id'])));
1020
+ return Array.isArray(res?.data) && res.data.length > 0;
1021
+ }, { retries: 2, backoffMs: 200 }));
1022
+ if (!found) {
1023
+ throw new Error(`Transaction "${id}" not found. Use actual_transactions_get to list transactions.`);
1024
+ }
1013
1025
  await withConcurrency(() => retry(() => rawUpdateTransaction(id, fields), { retries: 0, backoffMs: 200, isRetryable: isRetryableError }));
1014
1026
  });
1015
1027
  }
@@ -1019,9 +1031,27 @@ export async function updateTransactionBatch(updates) {
1019
1031
  // Sequential loop (not Promise.all) is intentional: concurrent rawUpdateTransaction calls
1020
1032
  // within one session can interleave withMutation CRDT messages unpredictably.
1021
1033
  return queueWriteOperation(async () => {
1034
+ // Nothing to do for an empty batch. The tool enforces min 1, but the adapter is a
1035
+ // public export, so guard here and avoid an empty-$oneof existence query.
1036
+ if (updates.length === 0)
1037
+ return { succeeded: [], failed: [] };
1038
+ // Pre-flight existence check (#212): ONE query for all ids (not one per item),
1039
+ // since the raw API silently no-ops on a missing id and would otherwise report
1040
+ // a no-op as success. Missing ids are routed to failed[] without an update call.
1041
+ const ids = updates.map((u) => u.id);
1042
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1043
+ const { q } = (await import('@actual-app/api'));
1044
+ const existing = await withConcurrency(() => retry(async () => {
1045
+ const res = (await rawRunQuery(q('transactions').filter({ id: { $oneof: ids } }).select(['id'])));
1046
+ return new Set(Array.isArray(res?.data) ? res.data.map((r) => r.id) : []);
1047
+ }, { retries: 2, backoffMs: 200 }));
1022
1048
  const succeeded = [];
1023
1049
  const failed = [];
1024
1050
  for (const { id, fields } of updates) {
1051
+ if (!existing.has(id)) {
1052
+ failed.push({ id, error: `Transaction "${id}" not found. Use actual_transactions_get to list transactions.` });
1053
+ continue;
1054
+ }
1025
1055
  try {
1026
1056
  await withConcurrency(() => retry(() => rawUpdateTransaction(id, fields), { retries: 0, backoffMs: 200, isRetryable: isRetryableError }));
1027
1057
  succeeded.push({ id });
@@ -8,7 +8,7 @@
8
8
  * Credit: ZanzyTHEbar (https://github.com/ZanzyTHEbar)
9
9
  *
10
10
  * Adapted for this project's conventions:
11
- * - No wrapToolCall — uses direct call() pattern
11
+ * - No wrapToolCall: uses direct call() pattern
12
12
  * - Returns a plain BatchResult object (no { result } wrapper)
13
13
  */
14
14
  import { z } from 'zod';
@@ -36,7 +36,7 @@ const InputSchema = z.object({
36
36
  });
37
37
  const tool = {
38
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.
39
+ description: `Update multiple transactions in a single call. Accepts up to 50 {id, fields} pairs. Each update is applied independently: partial failures are reported per-item so you know exactly which succeeded and which failed.
40
40
 
41
41
  Returns: { succeeded: [{id}], failed: [{id, error}], total, successCount, failureCount }
42
42
 
@@ -45,7 +45,7 @@ Example: { updates: [{ id: "txn-uuid-1", fields: { category: "cat-uuid" } }, { i
45
45
  call: async (args, _meta) => {
46
46
  try {
47
47
  const input = InputSchema.parse(args || {});
48
- // Single adapter call — all updates share one init/sync/shutdown cycle (fixes issue #79).
48
+ // Single adapter call: all updates share one init/sync/shutdown cycle (fixes issue #79).
49
49
  // Calling adapter.updateTransaction() in a loop would trigger N separate budget sessions.
50
50
  const { succeeded, failed } = await adapter.updateTransactionBatch(input.updates);
51
51
  const result = {
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.41",
4
+ "version": "0.6.42",
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/config_https_validation.test.js && node tests/unit/config_insecure_upstream.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/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/payees_delete.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_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.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/check_coverage.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/config_https_validation.test.js && node tests/unit/config_insecure_upstream.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/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/payees_delete.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_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.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/check_coverage.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",