actual-mcp-server 0.6.34 → 0.6.36
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 +15 -6
- package/dist/package.json +3 -3
- package/dist/src/actualToolsManager.js +3 -0
- package/dist/src/lib/actual-adapter.js +22 -1
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/notes_get.js +33 -0
- package/dist/src/tools/notes_update.js +65 -0
- package/dist/src/tools/payees_common_list.js +19 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -33,14 +33,14 @@ 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
|
+
- **70 tools, the most comprehensive coverage available.** Accounts, transactions, categories, payees, tags, notes, rules, budgets, batch operations, bank sync, and more. Covers 87% 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`.
|
|
40
40
|
- **Multi-user ready with OIDC.** Secure every session with JWKS-validated JWTs and per-user budget ACLs. No shared tokens required.
|
|
41
41
|
- **Production-grade reliability.** Connection pooling (up to 15 concurrent sessions), automatic retry with exponential backoff, and a full test suite (unit + E2E + integration).
|
|
42
42
|
|
|
43
|
-
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All
|
|
43
|
+
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All 70 tools tested end-to-end. Any MCP-compatible client should work.
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -342,9 +342,9 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
342
342
|
|
|
343
343
|
`actual_category_groups_get` · `actual_category_groups_create` · `actual_category_groups_update` · `actual_category_groups_delete`
|
|
344
344
|
|
|
345
|
-
### Payees (
|
|
345
|
+
### Payees (7)
|
|
346
346
|
|
|
347
|
-
`actual_payees_get` · `actual_payees_create` · `actual_payees_update` · `actual_payees_delete` · `actual_payees_merge` · `actual_payee_rules_get`
|
|
347
|
+
`actual_payees_get` · `actual_payees_common_list` · `actual_payees_create` · `actual_payees_update` · `actual_payees_delete` · `actual_payees_merge` · `actual_payee_rules_get`
|
|
348
348
|
|
|
349
349
|
### Tags (4)
|
|
350
350
|
|
|
@@ -357,6 +357,15 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
357
357
|
| `actual_tags_update` | Update tag name, color, or description by UUID |
|
|
358
358
|
| `actual_tags_delete` | Soft-delete a tag by UUID |
|
|
359
359
|
|
|
360
|
+
### Notes (2)
|
|
361
|
+
|
|
362
|
+
`actual_notes_get` · `actual_notes_update`
|
|
363
|
+
|
|
364
|
+
| Tool | Description |
|
|
365
|
+
|------|-------------|
|
|
366
|
+
| `actual_notes_get` | Get the note for any entity (account/category/category-group/payee UUID, or budget-YYYY-MM) |
|
|
367
|
+
| `actual_notes_update` | Set or clear the note for any entity; validates entity exists or matches budget-YYYY-MM pattern |
|
|
368
|
+
|
|
360
369
|
### Budgets (10)
|
|
361
370
|
|
|
362
371
|
| Tool | Description |
|
|
@@ -638,7 +647,7 @@ Several MCP servers exist for personal finance management. Here's how this proje
|
|
|
638
647
|
| **Version** | v0.4.26 | v1.11.1 | v0.2.0 | v0.1.0 |
|
|
639
648
|
| **Budget App** | Actual Budget (self-hosted) | Actual Budget (self-hosted) | Actual Budget (self-hosted) | YNAB (cloud, subscription) |
|
|
640
649
|
| **Language** | TypeScript / Node.js | TypeScript / Node.js | TypeScript / Node.js | Python |
|
|
641
|
-
| **Tool Count** | **
|
|
650
|
+
| **Tool Count** | **70** | ~22 | 18 | 9 |
|
|
642
651
|
| **Setup & Distribution** |||||
|
|
643
652
|
| **Transport** | HTTP + stdio | STDIO + SSE option | STDIO | STDIO |
|
|
644
653
|
| **Docker support** | ✅ Full (image + Compose) | ✅ Image only | ❌ | ❌ |
|
|
@@ -741,4 +750,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
741
750
|
|
|
742
751
|
---
|
|
743
752
|
|
|
744
|
-
**Version:** 0.6.
|
|
753
|
+
**Version:** 0.6.36 | **Tool Count:** 70 (verified LibreChat-compatible)
|
package/dist/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.36",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
8
8
|
},
|
|
9
|
-
"description": "MCP server with
|
|
9
|
+
"description": "MCP server with 70 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/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",
|
|
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",
|
|
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",
|
|
@@ -30,6 +30,7 @@ const IMPLEMENTED_TOOLS = [
|
|
|
30
30
|
'actual_category_groups_delete',
|
|
31
31
|
'actual_category_groups_get',
|
|
32
32
|
'actual_category_groups_update',
|
|
33
|
+
'actual_payees_common_list',
|
|
33
34
|
'actual_payees_create',
|
|
34
35
|
'actual_payees_delete',
|
|
35
36
|
'actual_payees_get',
|
|
@@ -70,6 +71,8 @@ const IMPLEMENTED_TOOLS = [
|
|
|
70
71
|
'actual_tags_create',
|
|
71
72
|
'actual_tags_update',
|
|
72
73
|
'actual_tags_delete',
|
|
74
|
+
'actual_notes_get',
|
|
75
|
+
'actual_notes_update',
|
|
73
76
|
];
|
|
74
77
|
// 🔑 Mapping of Actual API function names → your MCP tool names
|
|
75
78
|
// This allows us to compare what exists in the API vs. what it has been wrapped.
|
|
@@ -6,7 +6,7 @@ import api from '@actual-app/api';
|
|
|
6
6
|
// cannot expose its named exports via static import syntax. At runtime the default import
|
|
7
7
|
// IS module.exports, so all methods are accessible as properties.
|
|
8
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
-
const { addTransactions: rawAddTransactions, getAccounts: rawGetAccounts, importTransactions: rawImportTransactions, getTransactions: rawGetTransactions, getCategories: rawGetCategories, createCategory: rawCreateCategory, getPayees: rawGetPayees, createPayee: rawCreatePayee, getBudgetMonths: rawGetBudgetMonths, getBudgetMonth: rawGetBudgetMonth, setBudgetAmount: rawSetBudgetAmount, createAccount: rawCreateAccount, updateAccount: rawUpdateAccount, getAccountBalance: rawGetAccountBalance, updateTransaction: rawUpdateTransaction, deleteTransaction: rawDeleteTransaction, updateCategory: rawUpdateCategory, deleteCategory: rawDeleteCategory, updatePayee: rawUpdatePayee, deletePayee: rawDeletePayee, deleteAccount: rawDeleteAccount, getRules: rawGetRules, createRule: rawCreateRule, updateRule: rawUpdateRule, deleteRule: rawDeleteRule, setBudgetCarryover: rawSetBudgetCarryover, closeAccount: rawCloseAccount, reopenAccount: rawReopenAccount, getCategoryGroups: rawGetCategoryGroups, createCategoryGroup: rawCreateCategoryGroup, updateCategoryGroup: rawUpdateCategoryGroup, deleteCategoryGroup: rawDeleteCategoryGroup, mergePayees: rawMergePayees, getPayeeRules: rawGetPayeeRules, batchBudgetUpdates: rawBatchBudgetUpdates, holdBudgetForNextMonth: rawHoldBudgetForNextMonth, resetBudgetHold: rawResetBudgetHold, runQuery: rawRunQuery, runBankSync: rawRunBankSync, getBudgets: rawGetBudgets, getIDByName: rawGetIDByName, getServerVersion: rawGetServerVersion, getSchedules: rawGetSchedules, createSchedule: rawCreateSchedule, updateSchedule: rawUpdateSchedule, deleteSchedule: rawDeleteSchedule, getTags: rawGetTags, createTag: rawCreateTag, updateTag: rawUpdateTag, deleteTag: rawDeleteTag,
|
|
9
|
+
const { addTransactions: rawAddTransactions, getAccounts: rawGetAccounts, importTransactions: rawImportTransactions, getTransactions: rawGetTransactions, getCategories: rawGetCategories, createCategory: rawCreateCategory, getPayees: rawGetPayees, getCommonPayees: rawGetCommonPayees, createPayee: rawCreatePayee, getBudgetMonths: rawGetBudgetMonths, getBudgetMonth: rawGetBudgetMonth, setBudgetAmount: rawSetBudgetAmount, createAccount: rawCreateAccount, updateAccount: rawUpdateAccount, getAccountBalance: rawGetAccountBalance, updateTransaction: rawUpdateTransaction, deleteTransaction: rawDeleteTransaction, updateCategory: rawUpdateCategory, deleteCategory: rawDeleteCategory, updatePayee: rawUpdatePayee, deletePayee: rawDeletePayee, deleteAccount: rawDeleteAccount, getRules: rawGetRules, createRule: rawCreateRule, updateRule: rawUpdateRule, deleteRule: rawDeleteRule, setBudgetCarryover: rawSetBudgetCarryover, closeAccount: rawCloseAccount, reopenAccount: rawReopenAccount, getCategoryGroups: rawGetCategoryGroups, createCategoryGroup: rawCreateCategoryGroup, updateCategoryGroup: rawUpdateCategoryGroup, deleteCategoryGroup: rawDeleteCategoryGroup, mergePayees: rawMergePayees, getPayeeRules: rawGetPayeeRules, batchBudgetUpdates: rawBatchBudgetUpdates, holdBudgetForNextMonth: rawHoldBudgetForNextMonth, resetBudgetHold: rawResetBudgetHold, runQuery: rawRunQuery, runBankSync: rawRunBankSync, getBudgets: rawGetBudgets, getIDByName: rawGetIDByName, getServerVersion: rawGetServerVersion, getSchedules: rawGetSchedules, createSchedule: rawCreateSchedule, updateSchedule: rawUpdateSchedule, deleteSchedule: rawDeleteSchedule, getTags: rawGetTags, createTag: rawCreateTag, updateTag: rawUpdateTag, deleteTag: rawDeleteTag, getNote: rawGetNote, updateNote: rawUpdateNote,
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
11
|
} = api;
|
|
12
12
|
import { EventEmitter } from 'events';
|
|
@@ -880,6 +880,12 @@ export async function getPayees() {
|
|
|
880
880
|
return await withConcurrency(() => retry(() => rawGetPayees(), { retries: 2, backoffMs: 200 }));
|
|
881
881
|
});
|
|
882
882
|
}
|
|
883
|
+
export async function getCommonPayees() {
|
|
884
|
+
return withActualApi(async () => {
|
|
885
|
+
observability.incrementToolCall('actual.payees.commonList').catch(() => { });
|
|
886
|
+
return await withConcurrency(() => retry(() => rawGetCommonPayees(), { retries: 2, backoffMs: 200 }));
|
|
887
|
+
});
|
|
888
|
+
}
|
|
883
889
|
export async function createPayee(payee) {
|
|
884
890
|
observability.incrementToolCall('actual.payees.create').catch(() => { });
|
|
885
891
|
return queueWriteOperation(async () => {
|
|
@@ -1792,6 +1798,18 @@ export async function deleteTag(id) {
|
|
|
1792
1798
|
await withConcurrency(() => retry(() => rawDeleteTag(id), { retries: 0, backoffMs: 200 }));
|
|
1793
1799
|
});
|
|
1794
1800
|
}
|
|
1801
|
+
export async function getNote(id) {
|
|
1802
|
+
return withActualApi(async () => {
|
|
1803
|
+
observability.incrementToolCall('actual.notes.get').catch(() => { });
|
|
1804
|
+
return await withConcurrency(() => retry(() => rawGetNote(id), { retries: 2, backoffMs: 200 }));
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
export async function updateNote(id, note) {
|
|
1808
|
+
observability.incrementToolCall('actual.notes.update').catch(() => { });
|
|
1809
|
+
return queueWriteOperation(async () => {
|
|
1810
|
+
await withConcurrency(() => retry(() => rawUpdateNote(id, note), { retries: 0, backoffMs: 200, isRetryable: isRetryableError }));
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1795
1813
|
export default {
|
|
1796
1814
|
getAccounts,
|
|
1797
1815
|
getAccountsWithBalances,
|
|
@@ -1802,11 +1820,14 @@ export default {
|
|
|
1802
1820
|
getCategories,
|
|
1803
1821
|
createCategory,
|
|
1804
1822
|
getPayees,
|
|
1823
|
+
getCommonPayees,
|
|
1805
1824
|
createPayee,
|
|
1806
1825
|
getTags,
|
|
1807
1826
|
createTag,
|
|
1808
1827
|
updateTag,
|
|
1809
1828
|
deleteTag,
|
|
1829
|
+
getNote,
|
|
1830
|
+
updateNote,
|
|
1810
1831
|
getBudgetMonths,
|
|
1811
1832
|
getBudgetMonth,
|
|
1812
1833
|
setBudgetAmount,
|
package/dist/src/tools/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export { default as category_groups_delete } from './category_groups_delete.js';
|
|
|
27
27
|
export { default as category_groups_get } from './category_groups_get.js';
|
|
28
28
|
export { default as category_groups_update } from './category_groups_update.js';
|
|
29
29
|
export { default as payee_rules_get } from './payee_rules_get.js';
|
|
30
|
+
export { default as payees_common_list } from './payees_common_list.js';
|
|
30
31
|
export { default as payees_create } from './payees_create.js';
|
|
31
32
|
export { default as payees_delete } from './payees_delete.js';
|
|
32
33
|
export { default as payees_get } from './payees_get.js';
|
|
@@ -66,3 +67,5 @@ export { default as tags_list } from './tags_list.js';
|
|
|
66
67
|
export { default as tags_create } from './tags_create.js';
|
|
67
68
|
export { default as tags_update } from './tags_update.js';
|
|
68
69
|
export { default as tags_delete } from './tags_delete.js';
|
|
70
|
+
export { default as notes_get } from './notes_get.js';
|
|
71
|
+
export { default as notes_update } from './notes_update.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createTool } from '../lib/toolFactory.js';
|
|
3
|
+
import adapter from '../lib/actual-adapter.js';
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: 'actual_notes_get',
|
|
6
|
+
description: 'Get the note attached to an entity in Actual Budget. ' +
|
|
7
|
+
'The id can be any entity UUID (account, category, category-group, payee) ' +
|
|
8
|
+
'or the synthetic budget-month id in the form "budget-YYYY-MM" ' +
|
|
9
|
+
'(e.g. "budget-2026-01" for January 2026). ' +
|
|
10
|
+
'Returns the note text when one exists. ' +
|
|
11
|
+
'Returns a clear "no note" result (not null) when no note has been set for the given id.',
|
|
12
|
+
schema: z.object({
|
|
13
|
+
id: z.string().min(1).describe('Entity id: a UUID for an account/category/category-group/payee, ' +
|
|
14
|
+
'or "budget-YYYY-MM" for a budget month note'),
|
|
15
|
+
}),
|
|
16
|
+
handler: async ({ id }) => {
|
|
17
|
+
const note = await adapter.getNote(id);
|
|
18
|
+
if (note === null) {
|
|
19
|
+
return { found: false, id, note: null, message: `No note set for ${id}` };
|
|
20
|
+
}
|
|
21
|
+
return { found: true, id: note.id, note: note.note };
|
|
22
|
+
},
|
|
23
|
+
examples: [
|
|
24
|
+
{
|
|
25
|
+
description: 'Get note for an account',
|
|
26
|
+
input: { id: '00000000-0000-0000-0000-000000000001' },
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
description: 'Get note for a budget month',
|
|
30
|
+
input: { id: 'budget-2026-01' },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createTool } from '../lib/toolFactory.js';
|
|
3
|
+
import adapter from '../lib/actual-adapter.js';
|
|
4
|
+
const BUDGET_MONTH_RE = /^budget-\d{4}-\d{2}$/;
|
|
5
|
+
export default createTool({
|
|
6
|
+
name: 'actual_notes_update',
|
|
7
|
+
description: 'Set or clear the note attached to an entity in Actual Budget. ' +
|
|
8
|
+
'This is an upsert: creates the note if none exists, updates it if one does. ' +
|
|
9
|
+
'Pass an empty string for note to clear it. ' +
|
|
10
|
+
'The id must resolve to a known entity (account, category, category-group, payee) ' +
|
|
11
|
+
'or match the pattern "budget-YYYY-MM" for a budget month note. ' +
|
|
12
|
+
'Unknown ids are rejected to prevent orphan notes. ' +
|
|
13
|
+
'Budget month notes support template directives such as "#template 250" and "#goal 1000".',
|
|
14
|
+
schema: z.object({
|
|
15
|
+
id: z.string().min(1).describe('Entity id: a UUID for an account/category/category-group/payee, ' +
|
|
16
|
+
'or "budget-YYYY-MM" for a budget month note'),
|
|
17
|
+
note: z.string().describe('Note text to set. Pass an empty string to clear the note.'),
|
|
18
|
+
}),
|
|
19
|
+
handler: async ({ id, note }) => {
|
|
20
|
+
// Fast path: budget-YYYY-MM synthetic ids need no entity lookup.
|
|
21
|
+
if (!BUDGET_MONTH_RE.test(id)) {
|
|
22
|
+
// Validate that the id resolves to a known entity.
|
|
23
|
+
// Fetch in parallel to minimise latency.
|
|
24
|
+
const [accounts, categories, categoryGroups, payees] = await Promise.all([
|
|
25
|
+
adapter.getAccounts(),
|
|
26
|
+
adapter.getCategories(),
|
|
27
|
+
adapter.getCategoryGroups(),
|
|
28
|
+
adapter.getPayees(),
|
|
29
|
+
]);
|
|
30
|
+
const known = (Array.isArray(accounts) && accounts.some((e) => e.id === id)) ||
|
|
31
|
+
(Array.isArray(categories) && categories.some((e) => e.id === id)) ||
|
|
32
|
+
(Array.isArray(categoryGroups) && categoryGroups.some((e) => e.id === id)) ||
|
|
33
|
+
(Array.isArray(payees) && payees.some((e) => e.id === id));
|
|
34
|
+
if (!known) {
|
|
35
|
+
return {
|
|
36
|
+
error: `Entity "${id}" not found. ` +
|
|
37
|
+
'The id must be a UUID from actual_accounts_list, actual_categories_get, ' +
|
|
38
|
+
'actual_category_groups_get, or actual_payees_get, ' +
|
|
39
|
+
'or a budget month id like "budget-2026-01".',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await adapter.updateNote(id, note);
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
id,
|
|
47
|
+
note,
|
|
48
|
+
cleared: note === '',
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
description: 'Set a budget template note for January 2026',
|
|
54
|
+
input: { id: 'budget-2026-01', note: '#template 250' },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
description: 'Clear a note',
|
|
58
|
+
input: { id: 'budget-2026-01', note: '' },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
description: 'Set a note on an account',
|
|
62
|
+
input: { id: '00000000-0000-0000-0000-000000000001', note: 'Reconcile monthly' },
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createTool } from '../lib/toolFactory.js';
|
|
3
|
+
import adapter from '../lib/actual-adapter.js';
|
|
4
|
+
export default createTool({
|
|
5
|
+
name: 'actual_payees_common_list',
|
|
6
|
+
description: 'Return the most frequently used payees from recent transaction history (last ~12 weeks, top 10, ' +
|
|
7
|
+
'non-transfer payees only). ' +
|
|
8
|
+
'Distinct from actual_payees_get, which lists ALL payees with no recency or frequency filter. ' +
|
|
9
|
+
'An empty list is a normal, successful result: it means no non-transfer payees appear in recent ' +
|
|
10
|
+
'transactions, not that an error occurred. ' +
|
|
11
|
+
'Each item has the shape { id, name, transfer_acct? }.',
|
|
12
|
+
schema: z.object({}),
|
|
13
|
+
handler: async () => {
|
|
14
|
+
return await adapter.getCommonPayees();
|
|
15
|
+
},
|
|
16
|
+
examples: [
|
|
17
|
+
{ description: 'List recently frequent payees', input: {} },
|
|
18
|
+
],
|
|
19
|
+
});
|
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.6.
|
|
4
|
+
"version": "0.6.36",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
8
8
|
},
|
|
9
|
-
"description": "MCP server with
|
|
9
|
+
"description": "MCP server with 70 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/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",
|
|
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",
|
|
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",
|