@dizzlkheinz/ynab-mcpb 0.12.2 → 0.13.1

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.
Files changed (63) hide show
  1. package/.github/workflows/ci-tests.yml +6 -2
  2. package/CHANGELOG.md +14 -1
  3. package/NUL +0 -1
  4. package/README.md +36 -10
  5. package/dist/bundle/index.cjs +30 -30
  6. package/dist/index.js +9 -20
  7. package/dist/server/YNABMCPServer.d.ts +2 -1
  8. package/dist/server/YNABMCPServer.js +61 -27
  9. package/dist/server/cacheKeys.d.ts +8 -0
  10. package/dist/server/cacheKeys.js +8 -0
  11. package/dist/server/config.d.ts +22 -3
  12. package/dist/server/config.js +16 -17
  13. package/dist/server/securityMiddleware.js +3 -6
  14. package/dist/server/toolRegistry.js +8 -10
  15. package/dist/tools/accountTools.js +4 -3
  16. package/dist/tools/categoryTools.js +8 -7
  17. package/dist/tools/monthTools.js +2 -1
  18. package/dist/tools/payeeTools.js +2 -1
  19. package/dist/tools/reconciliation/executor.js +85 -4
  20. package/dist/tools/transactionTools.d.ts +3 -17
  21. package/dist/tools/transactionTools.js +5 -17
  22. package/dist/utils/baseError.d.ts +3 -0
  23. package/dist/utils/baseError.js +7 -0
  24. package/dist/utils/errors.d.ts +13 -0
  25. package/dist/utils/errors.js +15 -0
  26. package/dist/utils/validationError.d.ts +3 -0
  27. package/dist/utils/validationError.js +3 -0
  28. package/docs/plans/2025-11-20-reloadable-config-token-validation.md +93 -0
  29. package/docs/plans/2025-11-21-fix-transaction-cached-property.md +362 -0
  30. package/docs/plans/2025-11-21-reconciliation-error-handling.md +90 -0
  31. package/package.json +3 -2
  32. package/scripts/run-throttled-integration-tests.js +9 -3
  33. package/src/__tests__/performance.test.ts +12 -5
  34. package/src/__tests__/testUtils.ts +62 -5
  35. package/src/__tests__/workflows.e2e.test.ts +33 -0
  36. package/src/index.ts +8 -31
  37. package/src/server/YNABMCPServer.ts +81 -42
  38. package/src/server/__tests__/YNABMCPServer.integration.test.ts +10 -12
  39. package/src/server/__tests__/YNABMCPServer.test.ts +27 -15
  40. package/src/server/__tests__/config.test.ts +76 -152
  41. package/src/server/__tests__/server-startup.integration.test.ts +42 -14
  42. package/src/server/__tests__/toolRegistry.test.ts +1 -1
  43. package/src/server/cacheKeys.ts +8 -0
  44. package/src/server/config.ts +20 -38
  45. package/src/server/securityMiddleware.ts +3 -7
  46. package/src/server/toolRegistry.ts +14 -10
  47. package/src/tools/__tests__/categoryTools.test.ts +37 -19
  48. package/src/tools/__tests__/transactionTools.test.ts +58 -2
  49. package/src/tools/accountTools.ts +8 -3
  50. package/src/tools/categoryTools.ts +12 -7
  51. package/src/tools/monthTools.ts +7 -1
  52. package/src/tools/payeeTools.ts +7 -1
  53. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +25 -5
  54. package/src/tools/reconciliation/__tests__/executor.test.ts +46 -0
  55. package/src/tools/reconciliation/executor.ts +109 -6
  56. package/src/tools/schemas/outputs/utilityOutputs.ts +1 -1
  57. package/src/tools/transactionTools.ts +7 -18
  58. package/src/utils/baseError.ts +7 -0
  59. package/src/utils/errors.ts +21 -0
  60. package/src/utils/validationError.ts +3 -0
  61. package/temp-recon.ts +126 -0
  62. package/test_mcp_tools.mjs +75 -0
  63. package/ADOS-2-Module-1-Complete-Manual.md +0 -757
@@ -0,0 +1,3 @@
1
+ import { BaseError } from './baseError.js';
2
+
3
+ export class ValidationError extends BaseError {}
package/temp-recon.ts ADDED
@@ -0,0 +1,126 @@
1
+ import 'dotenv/config';
2
+ import * as ynab from 'ynab';
3
+ import { executeReconciliation } from './src/tools/reconciliation/executor.js';
4
+
5
+ async function main() {
6
+ const token = process.env['YNAB_ACCESS_TOKEN'];
7
+ if (!token) throw new Error('No token');
8
+ const api = new ynab.API(token);
9
+ const budgets = await api.budgets.getBudgets();
10
+ const budgetId = budgets.data.budgets[0]?.id;
11
+ if (!budgetId) throw new Error('no budget');
12
+ const accounts = await api.accounts.getAccounts(budgetId);
13
+ const account = accounts.data.accounts.find((a) => !a.closed);
14
+ if (!account) throw new Error('no account');
15
+ const snapshot = {
16
+ balance: account.balance,
17
+ cleared_balance: account.cleared_balance ?? account.balance,
18
+ uncleared_balance: account.uncleared_balance ?? 0,
19
+ };
20
+ const count = 2;
21
+ const transactionAmount = 7;
22
+ const clearedDollars = snapshot.cleared_balance / 1000;
23
+ const totalDelta = transactionAmount * count;
24
+ const statementBalance = clearedDollars + totalDelta;
25
+ const today = new Date().toISOString().slice(0, 10);
26
+ const unmatchedBank = Array.from({ length: count }, (_, i) => {
27
+ const date = new Date(Date.now() + i * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
28
+ return {
29
+ id: `integration-bank-${i}`,
30
+ date,
31
+ amount: transactionAmount,
32
+ payee: `Integration Payee ${i}`,
33
+ memo: `Integration memo ${i}`,
34
+ original_csv_row: i + 1,
35
+ };
36
+ });
37
+ const analysis = {
38
+ success: true,
39
+ phase: 'analysis',
40
+ summary: {
41
+ statement_date_range: 'Integration test',
42
+ bank_transactions_count: count,
43
+ ynab_transactions_count: 0,
44
+ auto_matched: 0,
45
+ suggested_matches: 0,
46
+ unmatched_bank: count,
47
+ unmatched_ynab: 0,
48
+ current_cleared_balance: clearedDollars,
49
+ target_statement_balance: statementBalance,
50
+ discrepancy: totalDelta,
51
+ discrepancy_explanation: 'Synthetic integration delta',
52
+ },
53
+ auto_matches: [],
54
+ suggested_matches: [],
55
+ unmatched_bank: unmatchedBank,
56
+ unmatched_ynab: [],
57
+ balance_info: {
58
+ current_cleared: clearedDollars,
59
+ current_uncleared: snapshot.uncleared_balance / 1000,
60
+ current_total: snapshot.balance / 1000,
61
+ target_statement: statementBalance,
62
+ discrepancy: totalDelta,
63
+ on_track: false,
64
+ },
65
+ next_steps: [],
66
+ insights: [],
67
+ } as const;
68
+
69
+ const params = {
70
+ budget_id: budgetId,
71
+ account_id: account.id,
72
+ csv_data: 'Date,Description,Amount',
73
+ statement_balance: statementBalance,
74
+ statement_date: today,
75
+ date_tolerance_days: 1,
76
+ amount_tolerance_cents: 1,
77
+ auto_match_threshold: 90,
78
+ suggestion_threshold: 60,
79
+ auto_create_transactions: true,
80
+ auto_update_cleared_status: false,
81
+ auto_unclear_missing: false,
82
+ auto_adjust_dates: false,
83
+ dry_run: false,
84
+ require_exact_match: true,
85
+ confidence_threshold: 0.8,
86
+ max_resolution_attempts: 3,
87
+ include_structured_data: false,
88
+ } as const;
89
+
90
+ const result = await executeReconciliation({
91
+ ynabAPI: api,
92
+ analysis: analysis as any,
93
+ params: params as any,
94
+ budgetId,
95
+ accountId: account.id,
96
+ initialAccount: snapshot,
97
+ currencyCode: 'USD',
98
+ });
99
+
100
+ console.log(
101
+ JSON.stringify(
102
+ {
103
+ summary: result.summary,
104
+ bulk: result.bulk_operation_details,
105
+ actions: result.actions_taken.slice(0, 5),
106
+ },
107
+ null,
108
+ 2,
109
+ ),
110
+ );
111
+
112
+ const ids: string[] = [];
113
+ for (const action of result.actions_taken) {
114
+ if (action.type === 'create_transaction' && (action.transaction as any)?.id) {
115
+ ids.push((action.transaction as any).id);
116
+ }
117
+ }
118
+ for (const id of ids) {
119
+ await api.transactions.deleteTransaction(budgetId, id);
120
+ }
121
+ }
122
+
123
+ main().catch((err) => {
124
+ console.error(err);
125
+ process.exit(1);
126
+ });
@@ -0,0 +1,75 @@
1
+ import { spawn } from 'child_process';
2
+
3
+ const server = spawn('node', ['dist/index.js'], {
4
+ stdio: ['pipe', 'pipe', 'inherit'],
5
+ env: process.env,
6
+ });
7
+
8
+ // Send initialize request
9
+ const initRequest = {
10
+ jsonrpc: '2.0',
11
+ id: 1,
12
+ method: 'initialize',
13
+ params: {
14
+ protocolVersion: '2024-11-05',
15
+ capabilities: {},
16
+ clientInfo: {
17
+ name: 'test-client',
18
+ version: '1.0.0',
19
+ },
20
+ },
21
+ };
22
+
23
+ // Send list tools request
24
+ const listToolsRequest = {
25
+ jsonrpc: '2.0',
26
+ id: 2,
27
+ method: 'tools/list',
28
+ params: {},
29
+ };
30
+
31
+ let buffer = '';
32
+ server.stdout.on('data', (data) => {
33
+ buffer += data.toString();
34
+ const lines = buffer.split('\n');
35
+ buffer = lines.pop() || '';
36
+
37
+ for (const line of lines) {
38
+ if (line.trim()) {
39
+ try {
40
+ const response = JSON.parse(line);
41
+ console.log('Response:', JSON.stringify(response, null, 2));
42
+
43
+ if (response.id === 1) {
44
+ // After init, send list tools
45
+ server.stdin.write(JSON.stringify(listToolsRequest) + '\n');
46
+ } else if (response.id === 2) {
47
+ // Got tools list
48
+ if (response.result && response.result.tools) {
49
+ console.log(`\n✅ Found ${response.result.tools.length} tools`);
50
+ response.result.tools.forEach((tool) => {
51
+ const desc = tool.description.substring(0, 60);
52
+ console.log(` - ${tool.name}: ${desc}...`);
53
+ });
54
+ } else {
55
+ console.log('\n❌ No tools found in response');
56
+ }
57
+ server.kill();
58
+ }
59
+ } catch (e) {
60
+ // Ignore parse errors for non-JSON output
61
+ }
62
+ }
63
+ }
64
+ });
65
+
66
+ setTimeout(() => {
67
+ console.log('\n⏱️ Timeout - sending requests');
68
+ server.stdin.write(JSON.stringify(initRequest) + '\n');
69
+ }, 1000);
70
+
71
+ setTimeout(() => {
72
+ console.log('❌ Test timed out');
73
+ server.kill();
74
+ process.exit(1);
75
+ }, 10000);