actual-mcp-server 0.7.2 → 0.7.3

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
@@ -755,4 +755,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
755
755
 
756
756
  ---
757
757
 
758
- **Version:** 0.7.2 | **Tool Count:** 71 (verified LibreChat-compatible)
758
+ **Version:** 0.7.3 | **Tool Count:** 71 (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.7.2",
4
+ "version": "0.7.3",
5
5
  "engines": {
6
6
  "node": ">=22.0.0",
7
7
  "npm": ">=10.0.0"
@@ -1,5 +1,5 @@
1
+ // src/actualToolsManager.ts
1
2
  import logger from './logger.js';
2
- import { z } from 'zod';
3
3
  import { formatZodError } from './lib/zod-error-format.js';
4
4
  // ✅ List of tools already implemented in this class.
5
5
  // Adding the tool name here is considered fully implemented.
@@ -119,21 +119,6 @@ const API_TOOL_MAP = {
119
119
  getIDByName: 'actual_get_id_by_name',
120
120
  getServerVersion: 'actual_server_get_version',
121
121
  };
122
- // Define Account schema for validation (simplified example)
123
- const AccountSchema = z.object({
124
- id: z.string().optional(),
125
- name: z.string(),
126
- type: z.string(), // Consider enum if you have fixed valid types
127
- offbudget: z.boolean().optional().default(false),
128
- closed: z.boolean().optional().default(false),
129
- });
130
- // Input schema for get_accounts - no args
131
- const GetAccountsInputSchema = z.object({});
132
- // Input schema for get_account_balance
133
- const GetAccountBalanceInputSchema = z.object({
134
- id: z.string().optional().describe('Account ID to get balance for (optional; defaults to first account)'),
135
- cutoff: z.string().optional().describe('Optional ISO date string cutoff'),
136
- });
137
122
  class ActualToolsManager {
138
123
  tools = new Map();
139
124
  constructor() { }
@@ -141,7 +126,7 @@ class ActualToolsManager {
141
126
  // Dynamically import all tool modules from src/tools/index.ts
142
127
  const toolModules = (await import('./tools/index.js'));
143
128
  let count = 0;
144
- for (const [key, tool] of Object.entries(toolModules)) {
129
+ for (const [_key, tool] of Object.entries(toolModules)) {
145
130
  const t = tool;
146
131
  if (t && t.name) {
147
132
  this.tools.set(t.name, tool);
package/dist/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { isKnownBenignRejection } from './lib/rejection-allowlist.js';
3
3
  // Add global error handlers
4
- let isHandlingQueryError = false;
5
4
  process.on('unhandledRejection', (reason, promise) => {
6
5
  console.error('=== UNHANDLED REJECTION ===');
7
6
  console.error('Promise:', promise);
@@ -125,16 +124,14 @@ Docs & source: https://github.com/agigante80/actual-mcp-server`);
125
124
  import('./actualConnection.js'),
126
125
  import('./lib/ActualMCPConnection.js'),
127
126
  ]);
128
- const [{ startHttpServer }, { startStdioServer }, loggerModule, osModule, utilsModule, actualToolsManagerModule,] = await Promise.all([
127
+ const [{ startHttpServer }, { startStdioServer }, loggerModule, utilsModule, actualToolsManagerModule,] = await Promise.all([
129
128
  import('./server/httpServer.js'),
130
129
  import('./server/stdioServer.js'),
131
130
  import('./logger.js'),
132
- import('os'),
133
131
  import('./utils.js'),
134
132
  import('./actualToolsManager.js'),
135
133
  ]);
136
134
  const logger = loggerModule.default;
137
- const os = osModule;
138
135
  const { getLocalIp } = utilsModule;
139
136
  const actualToolsManager = actualToolsManagerModule.default;
140
137
  // Load version from environment (Docker build-time) or package.json (local dev)
@@ -689,30 +689,6 @@ export function getConcurrencyState() {
689
689
  writeConnectionReuses: writeConnectionReuseCount,
690
690
  };
691
691
  }
692
- /**
693
- * Sync local changes to the Actual Budget server.
694
- *
695
- * This function should be called after write operations (create, update, delete)
696
- * to ensure changes are properly synced to the remote server. Without syncing,
697
- * changes may only exist locally and could be lost.
698
- *
699
- * Note: Adds a small delay to ensure local changes are committed before syncing.
700
- */
701
- async function syncToServer() {
702
- try {
703
- // Small delay to ensure local changes are committed to the database
704
- // before attempting to sync to the server
705
- await new Promise(resolve => setTimeout(resolve, 100));
706
- // TypeScript doesn't recognize sync in the type definitions, but it exists at runtime
707
- console.log('[SYNC] Calling api.sync()...');
708
- await api.sync();
709
- console.log('[SYNC] api.sync() completed successfully');
710
- }
711
- catch (err) {
712
- // Log but don't throw - sync failures shouldn't break the operation
713
- console.error('[SYNC] Sync to server failed:', err);
714
- }
715
- }
716
692
  /**
717
693
  * Wrap a raw function with the standard adapter retry + concurrency behavior.
718
694
  * Useful for tests that want to exercise retry behavior without calling the real raw methods.
@@ -37,7 +37,7 @@ serverDescription, // was passed by index.ts
37
37
  serverInstructions, // was passed by index.ts
38
38
  toolSchemas, // was passed by index.ts
39
39
  version, // server version from package.json
40
- bindHost = 'localhost', advertisedUrl) {
40
+ _bindHost = 'localhost', advertisedUrl) {
41
41
  const app = express();
42
42
  // Explicit body-size cap (#168). An oversized payload is rejected with HTTP 413
43
43
  // rather than buffered unbounded. Tunable via MCP_HTTP_BODY_LIMIT (default 512kb).
@@ -1,4 +1,4 @@
1
- export async function testMcpClient(advertisedUrl, port, httpPath) {
1
+ export async function testMcpClient(advertisedUrl, _port, httpPath) {
2
2
  // Try builtin fetch or fallback to node-fetch if necessary
3
3
  const globalFetch = globalThis.fetch;
4
4
  const fetchAny = globalFetch ?? (await import('node-fetch')).default;
@@ -82,14 +82,10 @@ export async function testMcpClient(advertisedUrl, port, httpPath) {
82
82
  throw new Error('initialize result.capabilities missing or not object');
83
83
  }
84
84
  // Accept either a tools array or derive tools from capabilities.tools object
85
- let initTools = [];
86
- if (Array.isArray(initResult['tools'])) {
87
- initTools = initResult['tools'];
88
- }
89
- else if (initResult['capabilities'] && typeof initResult['capabilities']['tools'] === 'object') {
90
- initTools = Object.keys(initResult['capabilities']['tools']);
91
- }
92
- else {
85
+ const hasTools = Array.isArray(initResult['tools'])
86
+ || (initResult['capabilities'] != null
87
+ && typeof initResult['capabilities']['tools'] === 'object');
88
+ if (!hasTools) {
93
89
  throw new Error('initialize result.tools missing or not array and capabilities.tools not present');
94
90
  }
95
91
  // serverInstructions may be a string, an object { instructions }, or absent.
@@ -106,7 +102,6 @@ export async function testMcpClient(advertisedUrl, port, httpPath) {
106
102
  else {
107
103
  console.warn('Warning: serverInstructions present but in unexpected shape — continuing tests');
108
104
  }
109
- // replace usages below with initTools where needed
110
105
  console.info('Initialize OK: capabilities/tools/serverInstructions present');
111
106
  // call tools/list (JSON-RPC) to verify RPC path works
112
107
  const listPayload = { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} };
@@ -1,11 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  import adapter from '../lib/actual-adapter.js';
3
3
  const InputSchema = z.object({});
4
+ // RESPONSE_TYPE: Account[]
4
5
  const tool = {
5
6
  name: 'actual_accounts_list',
6
7
  description: "List all accounts in Actual Budget including checking, savings, credit cards, and investment accounts. Returns account ID, name, balance (in cents), on-budget/off-budget status, and open/closed state.",
7
8
  inputSchema: InputSchema,
8
- call: async (args, _meta) => {
9
+ call: async (_args, _meta) => {
9
10
  // Single API session: getAccounts + all balances in one withActualApi cycle.
10
11
  // Calling getAccountBalance() per-account would open N separate sessions and
11
12
  // overwhelm the Actual server with concurrent init/shutdown cycles.
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import adapter from '../lib/actual-adapter.js';
3
3
  const InputSchema = z.object({ month: z.string().optional() });
4
+ // RESPONSE_TYPE: BudgetMonth
4
5
  const tool = {
5
6
  name: 'actual_budgets_getMonth',
6
7
  description: "Get budget data for a specific month in YYYY-MM format (e.g., '2025-12'). Returns all categories with their budgeted amounts, actual spending, and available balances for the month.",
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import adapter from '../lib/actual-adapter.js';
3
3
  const InputSchema = z.object({});
4
+ // RESPONSE_TYPE: array
4
5
  const tool = {
5
6
  name: 'actual_budgets_getMonths',
6
7
  description: "Get a list of all available budget months with summary data. Returns an array of months showing total budgeted, spent, and income for each month. Useful for viewing budget history and trends over time.",
@@ -6,6 +6,7 @@ const InputSchema = z.object({
6
6
  group_id: CommonSchemas.categoryGroupId,
7
7
  is_income: z.boolean().nullable().optional(),
8
8
  }).passthrough(); // Allow other fields to pass through
9
+ // RESPONSE_TYPE: string
9
10
  const tool = {
10
11
  name: 'actual_categories_create',
11
12
  description: "Create a category. REQUIRED: group_id (category group UUID). Use actual_category_groups_get to find available groups. Optional: is_income (boolean, defaults to false for expense categories).",
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import adapter from '../lib/actual-adapter.js';
3
3
  const InputSchema = z.object({});
4
+ // RESPONSE_TYPE: Category[]
4
5
  const tool = {
5
6
  name: 'actual_categories_get',
6
7
  description: 'List all budget categories organized by category groups. Categories are spending/income buckets (e.g., Groceries, Rent, Salary) used for budgeting and transaction categorization. Returns both grouped view and flat list with category IDs, names, and group assignments.',
@@ -5,7 +5,7 @@ const tool = {
5
5
  name: 'actual_category_groups_get',
6
6
  description: `List all category groups in Actual Budget. Category groups organize related categories together (e.g., "Monthly Bills" group contains "Rent", "Utilities", "Internet" categories). Each group has an ID, name, and optional properties.`,
7
7
  inputSchema: InputSchema,
8
- call: async (args, _meta) => {
8
+ call: async (_args, _meta) => {
9
9
  const groups = await adapter.getCategoryGroups();
10
10
  return { groups };
11
11
  },
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import adapter from '../lib/actual-adapter.js';
3
3
  const InputSchema = z.object({});
4
+ // RESPONSE_TYPE: Payee[]
4
5
  const tool = {
5
6
  name: 'actual_payees_get',
6
7
  description: "List all payees in Actual Budget. Payees represent merchants, service providers, individuals, or other entities you transact with. Returns payee ID, name, and optional transfer account information for internal transfers.",
@@ -5,7 +5,7 @@ const tool = {
5
5
  name: 'actual_rules_get',
6
6
  description: `List all budget rules in Actual Budget. Rules automate transaction categorization and other budget operations based on conditions. Each rule has conditions (e.g., payee matches "Amazon") and actions (e.g., set category to "Shopping").`,
7
7
  inputSchema: InputSchema,
8
- call: async (args, _meta) => {
8
+ call: async (_args, _meta) => {
9
9
  const rules = await adapter.getRules();
10
10
  return { rules };
11
11
  },
@@ -9,7 +9,7 @@ const tool = {
9
9
  name: 'actual_session_close',
10
10
  description: 'Close an idle MCP session to free up connection slots. Useful when you get "Max concurrent sessions reached" errors. Only closes sessions other than the current one.',
11
11
  inputSchema: InputSchema,
12
- call: async (args, meta) => {
12
+ call: async (args, _meta) => {
13
13
  const input = InputSchema.parse(args || {});
14
14
  const stats = connectionPool.getStats();
15
15
  if (stats.totalSessions === 0) {
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.7.2",
4
+ "version": "0.7.3",
5
5
  "engines": {
6
6
  "node": ">=22.0.0",
7
7
  "npm": ">=10.0.0"