ynab-mcp-deluxe 0.1.9

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/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright © 2025 Frank Fiegel (frank@glama.ai)
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the “Software”), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # YNAB MCP Deluxe
2
+
3
+ An MCP server for YNAB integration built with [FastMCP](https://github.com/punkpeye/fastmcp).
4
+
5
+ ## Development
6
+
7
+ ### Prerequisites
8
+
9
+ - [Bun](https://bun.sh/) - JavaScript runtime and package manager
10
+
11
+ ### Getting Started
12
+
13
+ ```bash
14
+ bun install
15
+ bun run dev
16
+ ```
17
+
18
+ ### Start the server
19
+
20
+ ```bash
21
+ bun run start
22
+ ```
23
+
24
+ Or use the dev script to interact with the server using CLI:
25
+
26
+ ```bash
27
+ bun run dev
28
+ ```
29
+
30
+ ### Testing
31
+
32
+ ```bash
33
+ bun run test
34
+ ```
35
+
36
+ ### Linting and Formatting
37
+
38
+ Run the full signal check (typescript, eslint, prettier):
39
+
40
+ ```bash
41
+ bun run signal
42
+ ```
43
+
44
+ Individual commands:
45
+
46
+ ```bash
47
+ bun run ts-check # TypeScript type checking
48
+ bun run lint # ESLint
49
+ bun run prettier-check # Prettier check
50
+ bun run prettier # Prettier format
51
+ bun run lint:fix # ESLint with auto-fix
52
+ ```
53
+
54
+ ## Security Considerations
55
+
56
+ ### ⚠️ Sync History Contains Sensitive Financial Data
57
+
58
+ This server maintains a local sync history to enable efficient delta synchronization with YNAB. **This history contains complete snapshots of your budget data**, including:
59
+
60
+ - Account names and balances
61
+ - Transaction details (payees, amounts, dates, memos)
62
+ - Category names and budgeted amounts
63
+ - Payee information
64
+
65
+ **Location:** `~/.config/ynab-mcp-deluxe/sync-history/[budgetId]/`
66
+
67
+ **Important:**
68
+
69
+ - This data is stored unencrypted on your local filesystem
70
+ - Anyone with access to your user account can read this data
71
+ - Consider the security implications before using this on shared systems
72
+ - Back up this directory if you need to preserve sync state across reinstalls
73
+
74
+ ### Clearing Sync History
75
+
76
+ To clear all sync history and force a fresh full sync on next access:
77
+
78
+ ```bash
79
+ rm -rf ~/.config/ynab-mcp-deluxe/sync-history/
80
+ ```
81
+
82
+ Or use the MCP tool (when available):
83
+
84
+ ```
85
+ clear_sync_history
86
+ ```
87
+
88
+ ## Environment Variables
89
+
90
+ | Variable | Required | Default | Description |
91
+ | ----------------------------------- | -------- | ------- | ------------------------------------------------------------------- |
92
+ | `YNAB_ACCESS_TOKEN` | Yes | - | YNAB API personal access token |
93
+ | `YNAB_BUDGET_ID` | No | - | Hard constraint - only allow access to this budget (safety feature) |
94
+ | `YNAB_READ_ONLY` | No | `false` | Set to `true` or `1` to block all write operations |
95
+ | `YNAB_SYNC_INTERVAL_SECONDS` | No | `600` | How often to sync with YNAB (0 = always sync) |
96
+ | `YNAB_DRIFT_DETECTION` | No | `true` | Enable drift detection to validate merge logic |
97
+ | `YNAB_ALWAYS_FULL_SYNC` | No | `false` | Skip delta sync, always fetch full budget |
98
+ | `YNAB_DRIFT_CHECK_INTERVAL_SYNCS` | No | `1` | Check for drift every N syncs |
99
+ | `YNAB_DRIFT_CHECK_INTERVAL_MINUTES` | No | `0` | Check for drift every N minutes (0 = disabled) |
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Budget backup utilities
3
+ */
4
+ /**
5
+ * Get the backup directory path
6
+ * ~/.config/ynab-mcp-deluxe/backups/
7
+ */
8
+ export declare function getBackupDir(): string;
9
+ /**
10
+ * Generate backup filename with timestamp and budget ID
11
+ * Format: YYYY-MM-DD_HH-mm-ss_ynab-budget-[id]_backup.json
12
+ */
13
+ export declare function generateBackupFilename(budgetId: string): string;
14
+ /**
15
+ * Ensure the backup directory exists
16
+ */
17
+ export declare function ensureBackupDir(): Promise<void>;
18
+ /**
19
+ * Backup a single budget to disk
20
+ * @returns The full path to the backup file
21
+ */
22
+ export declare function backupBudget(budgetId: string): Promise<string>;
23
+ /**
24
+ * Backup all budgets
25
+ * @returns Array of backup file paths
26
+ */
27
+ export declare function backupAllBudgets(): Promise<string[]>;
28
+ //# sourceMappingURL=backup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BpE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAU1D"}
package/dist/backup.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Budget backup utilities
3
+ */
4
+ import { mkdir, writeFile } from 'node:fs/promises';
5
+ import { homedir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { ynabClient } from './ynab-client.js';
8
+ // Server version for backup metadata
9
+ const SERVER_VERSION = '1.0.0';
10
+ /**
11
+ * Get the backup directory path
12
+ * ~/.config/ynab-mcp-deluxe/backups/
13
+ */
14
+ export function getBackupDir() {
15
+ return join(homedir(), '.config', 'ynab-mcp-deluxe', 'backups');
16
+ }
17
+ /**
18
+ * Generate backup filename with timestamp and budget ID
19
+ * Format: YYYY-MM-DD_HH-mm-ss_ynab-budget-[id]_backup.json
20
+ */
21
+ export function generateBackupFilename(budgetId) {
22
+ const now = new Date();
23
+ // Format: 2026-01-23_06-15-40
24
+ const timestamp = now
25
+ .toISOString()
26
+ .replace('T', '_')
27
+ .replace(/:/g, '-')
28
+ .replace(/\.\d+Z$/, '');
29
+ return `${timestamp}_ynab-budget-${budgetId}_backup.json`;
30
+ }
31
+ /**
32
+ * Ensure the backup directory exists
33
+ */
34
+ export async function ensureBackupDir() {
35
+ const dir = getBackupDir();
36
+ await mkdir(dir, { recursive: true });
37
+ }
38
+ /**
39
+ * Backup a single budget to disk
40
+ * @returns The full path to the backup file
41
+ */
42
+ export async function backupBudget(budgetId) {
43
+ // Get budget info for metadata
44
+ const budgetInfo = await ynabClient.getBudgetInfo(budgetId);
45
+ // Get raw budget data
46
+ const { budget, server_knowledge } = await ynabClient.getBudgetByIdRaw(budgetId);
47
+ // Construct backup object
48
+ const backup = {
49
+ backup_metadata: {
50
+ backup_timestamp: new Date().toISOString(),
51
+ budget_id: budgetId,
52
+ budget_name: budgetInfo.name,
53
+ server_knowledge,
54
+ ynab_mcp_server_version: SERVER_VERSION,
55
+ },
56
+ budget,
57
+ };
58
+ // Ensure directory exists
59
+ await ensureBackupDir();
60
+ // Write backup file
61
+ const filename = generateBackupFilename(budgetId);
62
+ const filePath = join(getBackupDir(), filename);
63
+ await writeFile(filePath, JSON.stringify(backup, null, 2), 'utf-8');
64
+ return filePath;
65
+ }
66
+ /**
67
+ * Backup all budgets
68
+ * @returns Array of backup file paths
69
+ */
70
+ export async function backupAllBudgets() {
71
+ const budgets = await ynabClient.getBudgets();
72
+ const paths = [];
73
+ for (const budget of budgets) {
74
+ const path = await backupBudget(budget.id);
75
+ paths.push(path);
76
+ }
77
+ return paths;
78
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Drift detection for LocalBudget.
3
+ *
4
+ * This module compares a merged LocalBudget (base + deltas) against a fresh
5
+ * full budget fetch to detect any discrepancies in our merge logic.
6
+ *
7
+ * When drift is detected:
8
+ * 1. Log detailed warnings showing what differs
9
+ * 2. Self-heal by replacing local budget with the full fetch result
10
+ */
11
+ import type { LocalBudget } from './types.js';
12
+ import type { Diff } from 'deep-diff';
13
+ /**
14
+ * Logger interface matching FastMCP's context log
15
+ */
16
+ interface ContextLog {
17
+ debug: (message: string, data?: unknown) => void;
18
+ error: (message: string, data?: unknown) => void;
19
+ info: (message: string, data?: unknown) => void;
20
+ warn: (message: string, data?: unknown) => void;
21
+ }
22
+ /**
23
+ * Result of a drift check
24
+ */
25
+ export interface DriftCheckResult {
26
+ /** Number of differences found */
27
+ differenceCount: number;
28
+ /** Summary of differences by category */
29
+ differenceSummary: Record<string, number>;
30
+ /** The raw differences (for debugging) */
31
+ differences: Diff<unknown>[];
32
+ /** Whether any drift was detected */
33
+ hasDrift: boolean;
34
+ /** Server knowledge from merged budget */
35
+ mergedServerKnowledge: number;
36
+ /** Whether the server knowledge values differ (external changes may have occurred) */
37
+ serverKnowledgeMismatch: boolean;
38
+ /** Server knowledge from truth budget */
39
+ truthServerKnowledge: number;
40
+ }
41
+ /**
42
+ * Check if drift detection is enabled.
43
+ *
44
+ * Default: true (enabled)
45
+ * Set YNAB_DRIFT_DETECTION=false to disable
46
+ */
47
+ export declare function isDriftDetectionEnabled(): boolean;
48
+ /**
49
+ * Check if "always full sync" mode is enabled.
50
+ * When enabled, skip delta queries entirely and always fetch the full budget.
51
+ *
52
+ * Default: false (use delta sync for performance)
53
+ * Set YNAB_ALWAYS_FULL_SYNC=true to enable
54
+ */
55
+ export declare function isAlwaysFullSyncEnabled(): boolean;
56
+ /**
57
+ * Get the drift check interval in number of syncs.
58
+ * In production, we don't need to check every sync - periodic checks are sufficient.
59
+ *
60
+ * Default: 1 (check every sync - good for development/alpha)
61
+ * Set YNAB_DRIFT_CHECK_INTERVAL_SYNCS to change
62
+ */
63
+ export declare function getDriftCheckIntervalSyncs(): number;
64
+ /**
65
+ * Get the drift check interval in minutes.
66
+ * Alternative to sync-count-based checking.
67
+ *
68
+ * Default: 0 (disabled - use sync count instead)
69
+ * Set YNAB_DRIFT_CHECK_INTERVAL_MINUTES to enable time-based checking
70
+ */
71
+ export declare function getDriftCheckIntervalMinutes(): number;
72
+ /**
73
+ * Determine if a drift check should be performed based on frequency settings.
74
+ * Call this before performing a drift check to respect rate limiting.
75
+ *
76
+ * @param budgetId - The budget ID to check drift state for
77
+ * @returns true if a drift check should be performed
78
+ */
79
+ export declare function shouldPerformDriftCheck(budgetId: string): boolean;
80
+ /**
81
+ * Record that a drift check was performed for a specific budget.
82
+ * Call this after completing a drift check.
83
+ *
84
+ * @param budgetId - The budget ID to record the drift check for
85
+ */
86
+ export declare function recordDriftCheck(budgetId: string): void;
87
+ /**
88
+ * Reset drift check state (useful for testing).
89
+ *
90
+ * @param budgetId - Optional budget ID to reset. If not provided, resets all budgets.
91
+ */
92
+ export declare function resetDriftCheckState(budgetId?: string): void;
93
+ /**
94
+ * Compare a merged LocalBudget against a "truth" budget from a full fetch.
95
+ *
96
+ * @param mergedBudget - The budget built from base + delta merges
97
+ * @param truthBudget - The budget from a fresh full fetch (source of truth)
98
+ * @returns Detailed comparison result
99
+ */
100
+ export declare function checkForDrift(mergedBudget: LocalBudget, truthBudget: LocalBudget): DriftCheckResult;
101
+ /**
102
+ * Log drift check results appropriately based on outcome.
103
+ *
104
+ * @param result - The drift check result
105
+ * @param budgetId - The budget ID (for logging context)
106
+ * @param log - The logger to use
107
+ */
108
+ export declare function logDriftCheckResult(result: DriftCheckResult, budgetId: string, log: ContextLog): void;
109
+ export {};
110
+ //# sourceMappingURL=drift-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift-detection.d.ts","sourceRoot":"","sources":["../src/drift-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAC,IAAI,EAA4C,MAAM,WAAW,CAAC;AAwC/E;;GAEG;AACH,UAAU,UAAU;IAClB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,0CAA0C;IAC1C,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7B,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sFAAsF;IACtF,uBAAuB,EAAE,OAAO,CAAC;IACjC,yCAAyC;IACzC,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA6CD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAQjD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAGjD;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAUnD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAUrD;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAqCjE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5D;AAoFD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,WAAW,GACvB,gBAAgB,CAoBlB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,UAAU,GACd,IAAI,CAwEN"}