actual-mcp-server 0.6.32 → 0.6.34

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
@@ -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
- - **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.
36
+ - **67 tools, the most comprehensive coverage available.** Accounts, transactions, categories, payees, tags, 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 63 tools tested end-to-end. Any MCP-compatible client should work.
43
+ > **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All 67 tools tested end-to-end. Any MCP-compatible client should work.
44
44
 
45
45
  ---
46
46
 
@@ -346,6 +346,17 @@ For Claude Desktop (stdio), restart Claude after upgrading.
346
346
 
347
347
  `actual_payees_get` · `actual_payees_create` · `actual_payees_update` · `actual_payees_delete` · `actual_payees_merge` · `actual_payee_rules_get`
348
348
 
349
+ ### Tags (4)
350
+
351
+ `actual_tags_list` · `actual_tags_create` · `actual_tags_update` · `actual_tags_delete`
352
+
353
+ | Tool | Description |
354
+ |------|-------------|
355
+ | `actual_tags_list` | List all tags (id, tag word, optional color and description) |
356
+ | `actual_tags_create` | Create or upsert a tag by name; returns the tag UUID |
357
+ | `actual_tags_update` | Update tag name, color, or description by UUID |
358
+ | `actual_tags_delete` | Soft-delete a tag by UUID |
359
+
349
360
  ### Budgets (10)
350
361
 
351
362
  | Tool | Description |
@@ -730,4 +741,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
730
741
 
731
742
  ---
732
743
 
733
- **Version:** 0.6.32 | **Tool Count:** 63 (verified LibreChat-compatible)
744
+ **Version:** 0.6.34 | **Tool Count:** 67 (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.32",
4
+ "version": "0.6.34",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
8
8
  },
9
- "description": "MCP server with 63 tools for AI-driven financial management with Actual Budget. HTTP and stdio transports for LibreChat, Claude Desktop, Cursor, VS Code, Gemini CLI",
9
+ "description": "MCP server with 67 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",
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",
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",
@@ -66,6 +66,10 @@ const IMPLEMENTED_TOOLS = [
66
66
  'actual_session_list',
67
67
  'actual_get_id_by_name',
68
68
  'actual_server_get_version',
69
+ 'actual_tags_list',
70
+ 'actual_tags_create',
71
+ 'actual_tags_update',
72
+ 'actual_tags_delete',
69
73
  ];
70
74
  // 🔑 Mapping of Actual API function names → your MCP tool names
71
75
  // This allows us to compare what exists in the API vs. what it has been wrapped.
@@ -6,12 +6,13 @@ 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,
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,
10
10
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
11
  } = api;
12
12
  import { EventEmitter } from 'events';
13
13
  import observability from '../observability.js';
14
14
  import retry, { isRetryableError } from './retry.js';
15
+ import { notFoundMsg } from './errors.js';
15
16
  import logger from '../logger.js';
16
17
  import config from '../config.js';
17
18
  import { parseBudgetRegistry } from './budget-registry.js';
@@ -369,6 +370,14 @@ let _skipApiInitForTests = false;
369
370
  export function _setSkipApiInitForTests(value) {
370
371
  _skipApiInitForTests = value;
371
372
  }
373
+ /**
374
+ * Test-only readback of the active budget for the current requestContext, so a
375
+ * restart-replay test (#189) can assert the per-principal preference is restored
376
+ * on a fresh session. NOT part of the package public surface.
377
+ */
378
+ export function _getActiveBudgetConfigForTests() {
379
+ return getActiveBudgetConfig();
380
+ }
372
381
  // ----------------------------------------------------------------------------
373
382
  // Auth-rate-limit retry — issue #127
374
383
  // ----------------------------------------------------------------------------
@@ -1741,6 +1750,48 @@ export async function getServerVersion() {
1741
1750
  return await withConcurrency(() => retry(() => rawGetServerVersion(), { retries: 2, backoffMs: 200 }));
1742
1751
  });
1743
1752
  }
1753
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1754
+ export async function getTags() {
1755
+ return withActualApi(async () => {
1756
+ observability.incrementToolCall('actual.tags.get').catch(() => { });
1757
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1758
+ return await withConcurrency(() => retry(() => rawGetTags(), { retries: 2, backoffMs: 200 }));
1759
+ });
1760
+ }
1761
+ export async function createTag(tag) {
1762
+ observability.incrementToolCall('actual.tags.create').catch(() => { });
1763
+ return queueWriteOperation(async () => {
1764
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1765
+ const raw = await withConcurrency(() => retry(() => rawCreateTag(tag), { retries: 2, backoffMs: 200, isRetryable: isRetryableError }));
1766
+ return normalizeToId(raw);
1767
+ });
1768
+ }
1769
+ export async function updateTag(id, fields) {
1770
+ observability.incrementToolCall('actual.tags.update').catch(() => { });
1771
+ return queueWriteOperation(async () => {
1772
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1773
+ const tags = await withConcurrency(() => retry(() => rawGetTags(), { retries: 2, backoffMs: 200 }));
1774
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1775
+ const exists = tags.some((t) => t.id === id);
1776
+ if (!exists) {
1777
+ throw new Error(notFoundMsg('Tag', id, 'actual_tags_list'));
1778
+ }
1779
+ await withConcurrency(() => retry(() => rawUpdateTag(id, fields), { retries: 0, backoffMs: 200, isRetryable: isRetryableError }));
1780
+ });
1781
+ }
1782
+ export async function deleteTag(id) {
1783
+ observability.incrementToolCall('actual.tags.delete').catch(() => { });
1784
+ return queueWriteOperation(async () => {
1785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1786
+ const tags = await withConcurrency(() => retry(() => rawGetTags(), { retries: 2, backoffMs: 200 }));
1787
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1788
+ const exists = tags.some((t) => t.id === id);
1789
+ if (!exists) {
1790
+ throw new Error(notFoundMsg('Tag', id, 'actual_tags_list'));
1791
+ }
1792
+ await withConcurrency(() => retry(() => rawDeleteTag(id), { retries: 0, backoffMs: 200 }));
1793
+ });
1794
+ }
1744
1795
  export default {
1745
1796
  getAccounts,
1746
1797
  getAccountsWithBalances,
@@ -1752,6 +1803,10 @@ export default {
1752
1803
  createCategory,
1753
1804
  getPayees,
1754
1805
  createPayee,
1806
+ getTags,
1807
+ createTag,
1808
+ updateTag,
1809
+ deleteTag,
1755
1810
  getBudgetMonths,
1756
1811
  getBudgetMonth,
1757
1812
  setBudgetAmount,
@@ -58,6 +58,14 @@ export const ruleIdSchema = z
58
58
  .string()
59
59
  .regex(UUID_PATTERN, 'Invalid rule ID format (expected UUID)')
60
60
  .describe('Rule UUID');
61
+ /**
62
+ * Tag UUID validation
63
+ * Used for: tag management operations (update, delete)
64
+ */
65
+ export const tagIdSchema = z
66
+ .string()
67
+ .regex(UUID_PATTERN, 'Invalid tag ID format (expected UUID)')
68
+ .describe('Tag UUID');
61
69
  // ============================================================================
62
70
  // DATE SCHEMAS
63
71
  // ============================================================================
@@ -169,6 +177,7 @@ export const CommonSchemas = {
169
177
  categoryGroupId: categoryGroupIdSchema,
170
178
  payeeId: payeeIdSchema,
171
179
  ruleId: ruleIdSchema,
180
+ tagId: tagIdSchema,
172
181
  // Dates
173
182
  date: dateSchema,
174
183
  monthYear: monthYearSchema,
@@ -62,3 +62,7 @@ export { default as transactions_update_batch } from './transactions_update_batc
62
62
  export { default as transfers_create } from './transfers_create.js';
63
63
  export { default as session_close } from './session_close.js';
64
64
  export { default as session_list } from './session_list.js';
65
+ export { default as tags_list } from './tags_list.js';
66
+ export { default as tags_create } from './tags_create.js';
67
+ export { default as tags_update } from './tags_update.js';
68
+ export { default as tags_delete } from './tags_delete.js';
@@ -0,0 +1,30 @@
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_tags_create',
6
+ description: 'Create a new tag in Actual Budget. The "tag" field is the raw word WITHOUT a leading "#" character ' +
7
+ '(e.g. use "groceries", not "#groceries"). ' +
8
+ 'IMPORTANT: this operation is an upsert on the tag word. If a tag with the same name already exists ' +
9
+ '(including previously deleted ones), the existing tag is updated and its id is returned. ' +
10
+ 'Creating the same tag name twice always returns the same id. ' +
11
+ 'Color convention is CSS hex (e.g. "#33aa33") but no format is enforced by the API.',
12
+ schema: z.object({
13
+ tag: z.string().min(1, 'tag must not be empty').describe('Tag word without "#" prefix (e.g. "groceries")'),
14
+ color: z.string().optional().describe('Optional color string (convention: CSS hex like "#33aa33")'),
15
+ description: z.string().optional().describe('Optional description of the tag'),
16
+ }),
17
+ handler: async (input) => {
18
+ return await adapter.createTag(input);
19
+ },
20
+ examples: [
21
+ {
22
+ description: 'Create a groceries tag with a green color',
23
+ input: { tag: 'groceries', color: '#33aa33', description: 'Food purchases' },
24
+ },
25
+ {
26
+ description: 'Create a minimal tag (no color or description)',
27
+ input: { tag: 'travel' },
28
+ },
29
+ ],
30
+ });
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import { createTool } from '../lib/toolFactory.js';
3
+ import { CommonSchemas } from '../lib/schemas/common.js';
4
+ import adapter from '../lib/actual-adapter.js';
5
+ export default createTool({
6
+ name: 'actual_tags_delete',
7
+ description: 'Delete a tag from Actual Budget by its UUID. ' +
8
+ 'The tag is soft-deleted (tombstoned) and will no longer appear in actual_tags_list. ' +
9
+ 'If the id does not exist, a not-found error is returned. ' +
10
+ 'Use actual_tags_list to find valid tag UUIDs.',
11
+ schema: z.object({
12
+ id: CommonSchemas.tagId,
13
+ }),
14
+ handler: async ({ id }) => {
15
+ await adapter.deleteTag(id);
16
+ return { success: true };
17
+ },
18
+ examples: [
19
+ {
20
+ description: 'Delete a tag by its UUID',
21
+ input: { id: '00000000-0000-0000-0000-000000000001' },
22
+ },
23
+ ],
24
+ });
@@ -0,0 +1,15 @@
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_tags_list',
6
+ description: 'List all tags defined in Actual Budget. Tags are stored without a leading "#" character ' +
7
+ '(e.g. "groceries", not "#groceries"). Returns id, tag (the word), and optional color and description fields.',
8
+ schema: z.object({}),
9
+ handler: async () => {
10
+ return await adapter.getTags();
11
+ },
12
+ examples: [
13
+ { description: 'List all tags', input: {} },
14
+ ],
15
+ });
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { createTool } from '../lib/toolFactory.js';
3
+ import { CommonSchemas } from '../lib/schemas/common.js';
4
+ import adapter from '../lib/actual-adapter.js';
5
+ export default createTool({
6
+ name: 'actual_tags_update',
7
+ description: 'Update an existing tag in Actual Budget. At least one of tag/color/description must be provided. ' +
8
+ 'The id must be a valid UUID from actual_tags_list. ' +
9
+ 'If the id does not exist, a not-found error is returned (the API would silently no-op; this tool adds a pre-flight guard). ' +
10
+ 'The "tag" field is the raw word WITHOUT a leading "#" character.',
11
+ schema: z.object({
12
+ id: CommonSchemas.tagId,
13
+ tag: z.string().min(1, 'tag must not be empty').optional().describe('New tag word without "#" prefix'),
14
+ color: z.string().optional().describe('New color string (convention: CSS hex like "#112233")'),
15
+ description: z.string().optional().describe('New description'),
16
+ }).refine((data) => data.tag !== undefined || data.color !== undefined || data.description !== undefined, { message: 'At least one of tag, color, or description must be provided' }),
17
+ handler: async ({ id, tag, color, description }) => {
18
+ const fields = {};
19
+ if (tag !== undefined)
20
+ fields.tag = tag;
21
+ if (color !== undefined)
22
+ fields.color = color;
23
+ if (description !== undefined)
24
+ fields.description = description;
25
+ await adapter.updateTag(id, fields);
26
+ return { success: true };
27
+ },
28
+ examples: [
29
+ {
30
+ description: 'Rename a tag and change its color',
31
+ input: { id: '00000000-0000-0000-0000-000000000001', tag: 'food', color: '#112233' },
32
+ },
33
+ {
34
+ description: 'Update only the description',
35
+ input: { id: '00000000-0000-0000-0000-000000000001', description: 'Updated description' },
36
+ },
37
+ ],
38
+ });
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.32",
4
+ "version": "0.6.34",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
8
8
  },
9
- "description": "MCP server with 63 tools for AI-driven financial management with Actual Budget. HTTP and stdio transports for LibreChat, Claude Desktop, Cursor, VS Code, Gemini CLI",
9
+ "description": "MCP server with 67 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",
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",
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",