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 +14 -3
- package/dist/package.json +3 -3
- package/dist/src/actualToolsManager.js +4 -0
- package/dist/src/lib/actual-adapter.js +56 -1
- package/dist/src/lib/schemas/common.js +9 -0
- package/dist/src/tools/index.js +4 -0
- package/dist/src/tools/tags_create.js +30 -0
- package/dist/src/tools/tags_delete.js +24 -0
- package/dist/src/tools/tags_list.js +15 -0
- package/dist/src/tools/tags_update.js +38 -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
|
+
- **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
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
package/dist/src/tools/index.js
CHANGED
|
@@ -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.
|
|
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
|
|
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",
|