@dizzlkheinz/ynab-mcpb 0.16.1 → 0.17.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 (169) hide show
  1. package/.env.example +33 -33
  2. package/.github/workflows/ci-tests.yml +45 -45
  3. package/.github/workflows/claude-code-review.yml +57 -57
  4. package/.github/workflows/claude.yml +50 -50
  5. package/.github/workflows/full-integration.yml +22 -22
  6. package/.github/workflows/publish.yml +11 -2
  7. package/CLAUDE.md +33 -47
  8. package/README.md +8 -10
  9. package/dist/bundle/index.cjs +54 -54
  10. package/dist/server/YNABMCPServer.d.ts +120 -54
  11. package/dist/server/YNABMCPServer.js +28 -381
  12. package/dist/server/config.d.ts +2 -0
  13. package/dist/server/config.js +1 -0
  14. package/dist/server/securityMiddleware.d.ts +37 -8
  15. package/dist/tools/accountTools.d.ts +2 -0
  16. package/dist/tools/accountTools.js +45 -0
  17. package/dist/tools/adapters.d.ts +12 -0
  18. package/dist/tools/adapters.js +25 -0
  19. package/dist/tools/budgetTools.d.ts +2 -0
  20. package/dist/tools/budgetTools.js +30 -0
  21. package/dist/tools/categoryTools.d.ts +2 -0
  22. package/dist/tools/categoryTools.js +45 -0
  23. package/dist/tools/monthTools.d.ts +2 -0
  24. package/dist/tools/monthTools.js +32 -0
  25. package/dist/tools/payeeTools.d.ts +2 -0
  26. package/dist/tools/payeeTools.js +32 -0
  27. package/dist/tools/reconciliation/index.d.ts +2 -0
  28. package/dist/tools/reconciliation/index.js +33 -0
  29. package/dist/tools/schemas/common.d.ts +3 -0
  30. package/dist/tools/schemas/common.js +3 -0
  31. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  32. package/dist/tools/schemas/outputs/index.d.ts +2 -2
  33. package/dist/tools/schemas/outputs/index.js +2 -2
  34. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +0 -15
  35. package/dist/tools/schemas/outputs/utilityOutputs.js +0 -9
  36. package/dist/tools/transactionTools.d.ts +2 -0
  37. package/dist/tools/transactionTools.js +124 -0
  38. package/dist/tools/utilityTools.d.ts +2 -7
  39. package/dist/tools/utilityTools.js +19 -38
  40. package/dist/types/index.d.ts +1 -0
  41. package/dist/types/toolRegistration.d.ts +27 -0
  42. package/dist/types/toolRegistration.js +1 -0
  43. package/docs/maintainers/npm-publishing.md +27 -0
  44. package/docs/reference/API.md +15 -70
  45. package/docs/technical/reconciliation-system-architecture.md +2251 -2251
  46. package/package.json +6 -6
  47. package/scripts/analyze-bundle.mjs +41 -41
  48. package/scripts/generate-mcpb.ps1 +95 -95
  49. package/scripts/run-domain-integration-tests.js +4 -1
  50. package/scripts/watch-and-restart.ps1 +49 -49
  51. package/src/__tests__/comprehensive.integration.test.ts +0 -28
  52. package/src/__tests__/performance.test.ts +4 -12
  53. package/src/__tests__/setup.ts +45 -14
  54. package/src/__tests__/workflows.e2e.test.ts +1 -51
  55. package/src/server/YNABMCPServer.ts +33 -519
  56. package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
  57. package/src/server/__tests__/toolRegistration.test.ts +236 -0
  58. package/src/server/config.ts +1 -0
  59. package/src/tools/__tests__/adapters.test.ts +113 -0
  60. package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
  61. package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
  62. package/src/tools/__tests__/utilityTools.test.ts +1 -123
  63. package/src/tools/accountTools.ts +53 -0
  64. package/src/tools/adapters.ts +74 -0
  65. package/src/tools/budgetTools.ts +37 -0
  66. package/src/tools/categoryTools.ts +53 -0
  67. package/src/tools/monthTools.ts +39 -0
  68. package/src/tools/payeeTools.ts +39 -0
  69. package/src/tools/reconciliation/index.ts +45 -0
  70. package/src/tools/schemas/common.ts +18 -0
  71. package/src/tools/schemas/outputs/index.ts +0 -3
  72. package/src/tools/schemas/outputs/utilityOutputs.ts +2 -43
  73. package/src/tools/toolCategories.ts +0 -1
  74. package/src/tools/transactionTools.ts +140 -0
  75. package/src/tools/utilityTools.ts +24 -55
  76. package/src/types/index.ts +3 -0
  77. package/src/types/toolRegistration.ts +88 -0
  78. package/vitest.config.ts +2 -1
  79. package/.chunkhound.json +0 -11
  80. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
  81. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
  82. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
  83. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
  84. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
  85. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
  86. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
  87. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
  88. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
  89. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
  90. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
  91. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +0 -1
  92. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +0 -5217
  93. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +0 -2594
  94. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +0 -2594
  95. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
  96. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
  97. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
  98. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
  99. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
  100. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
  101. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
  102. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
  103. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
  104. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
  105. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
  106. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
  107. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
  108. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
  109. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
  110. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
  111. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
  112. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
  113. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
  114. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
  115. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
  116. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
  117. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
  118. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
  119. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
  120. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
  121. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
  122. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
  123. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
  124. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
  125. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
  126. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
  127. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
  128. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
  129. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
  130. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
  131. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
  132. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
  133. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
  134. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
  135. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
  136. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
  137. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
  138. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
  139. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
  140. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
  141. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
  142. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
  143. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
  144. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
  145. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
  146. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
  147. package/.github/workflows/pr-description-check.yml +0 -88
  148. package/AGENTS.md +0 -36
  149. package/NUL +0 -1
  150. package/docs/README.md +0 -72
  151. package/docs/getting-started/CONFIGURATION.md +0 -175
  152. package/docs/getting-started/INSTALLATION.md +0 -333
  153. package/docs/getting-started/QUICKSTART.md +0 -282
  154. package/docs/guides/ARCHITECTURE.md +0 -533
  155. package/docs/guides/DEPLOYMENT.md +0 -189
  156. package/docs/guides/INTEGRATION_TESTING.md +0 -730
  157. package/docs/guides/TESTING.md +0 -591
  158. package/docs/reconciliation-flow.md +0 -83
  159. package/docs/reference/EXAMPLES.md +0 -946
  160. package/docs/reference/TOOLS.md +0 -348
  161. package/docs/reference/TROUBLESHOOTING.md +0 -481
  162. package/package.json.tmp +0 -105
  163. package/temp-recon.ts +0 -126
  164. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
  165. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
  166. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
  167. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
  168. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
  169. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
@@ -1,747 +0,0 @@
1
- Based on my analysis of the YNAB MCP Server codebase, I'll create a comprehensive implementation plan for the next iteration. The repo has three pending plans that represent excellent next steps. Let me synthesize these into a cohesive, actionable plan:
2
-
3
- ---
4
-
5
- # **YNAB MCP Server v0.14.x Implementation Plan**
6
-
7
- **Project:** YNAB MCP Server (Model Context Protocol server for YNAB integration)
8
- **Repository:** C:/Users/ksutk/projects/ynab-mcpb
9
- **Current Version:** 0.13.4
10
- **Target Version:** 0.14.0
11
- **Architecture:** TypeScript/Node.js 18+, MCP SDK, Vitest testing
12
-
13
- ## **Executive Summary**
14
-
15
- This plan consolidates three high-priority improvements identified in `docs/plans/`:
16
-
17
- 1. **Reloadable Config & Token Validation** - Improve testability and error handling
18
- 2. **Reconciliation Error Handling** - Surface YNAB API errors properly
19
- 3. **Fix Transaction Cached Property** - Consistency in large response paths (ALREADY COMPLETED per CHANGELOG)
20
-
21
- The implementation follows TDD methodology with 2-5 minute task granularity, targeting concrete file paths and test patterns.
22
-
23
- ---
24
-
25
- ## **Priority 1: Reloadable Config & Token Validation**
26
-
27
- **Goal:** Make config parsing reloadable for environment-mutation tests, harden token validation against malformed API responses, and improve CI portability.
28
-
29
- **Files Modified:**
30
- - `src/server/config.ts`
31
- - `src/server/YNABMCPServer.ts`
32
- - `src/server/__tests__/config.test.ts`
33
- - `src/server/__tests__/YNABMCPServer.test.ts`
34
- - `src/server/__tests__/server-startup.integration.test.ts`
35
- - `scripts/run-throttled-integration-tests.js`
36
-
37
- **Rationale:** Current config is module-scoped, making it difficult to test environment changes without module reloads. Token validation doesn't gracefully handle non-JSON YNAB responses (HTML error pages, etc.).
38
-
39
- ---
40
-
41
- ### **Task 1.1: Implement Reloadable Config Loader**
42
-
43
- **Test-First Approach:**
44
-
45
- **Step 1** (2 min): Write failing test for reloadable behavior
46
- **File:** `src/server/__tests__/config.test.ts`
47
- **Action:** Add test case (already exists at line 18-25 but verify it passes):
48
- ```typescript
49
- it('reloads environment variables on each loadConfig call', async () => {
50
- const { loadConfig } = await import('../config');
51
- process.env.YNAB_ACCESS_TOKEN = 'test-token-123';
52
- expect(loadConfig().YNAB_ACCESS_TOKEN).toBe('test-token-123');
53
-
54
- process.env.YNAB_ACCESS_TOKEN = 'updated-token-456';
55
- expect(loadConfig().YNAB_ACCESS_TOKEN).toBe('updated-token-456');
56
- });
57
- ```
58
- **Verification:** `npx vitest run src/server/__tests__/config.test.ts`
59
- **Expected:** Should already pass based on current implementation (line 14-21 in config.ts)
60
-
61
- **Step 2** (1 min): Verify singleton behavior is preserved
62
- **File:** `src/server/__tests__/config.test.ts`
63
- **Action:** Ensure test at line 27-35 passes (singleton `config` uses initial parse, `loadConfig()` re-parses)
64
- **Verification:** `npx vitest run src/server/__tests__/config.test.ts`
65
-
66
- **Step 3** (2 min): Add test for explicit env override
67
- **File:** `src/server/__tests__/config.test.ts`
68
- **Action:** Add test case:
69
- ```typescript
70
- it('accepts explicit env parameter for testing', async () => {
71
- const { loadConfig } = await import('../config');
72
- const testEnv = { YNAB_ACCESS_TOKEN: 'explicit-token' };
73
- expect(loadConfig(testEnv).YNAB_ACCESS_TOKEN).toBe('explicit-token');
74
- });
75
- ```
76
- **Verification:** `npx vitest run src/server/__tests__/config.test.ts`
77
- **Expected:** Pass (already supported at config.ts:14)
78
-
79
- **Step 4** (1 min): Run full config test suite
80
- **Command:** `npx vitest run src/server/__tests__/config.test.ts`
81
- **Expected:** All tests pass
82
-
83
- ---
84
-
85
- ### **Task 1.2: Inject Per-Instance Config into YNABMCPServer**
86
-
87
- **Implementation Approach:**
88
-
89
- **Step 1** (3 min): Add test for per-instance config isolation
90
- **File:** `src/server/__tests__/YNABMCPServer.test.ts`
91
- **Action:** Add test case:
92
- ```typescript
93
- it('uses per-instance config instead of module singleton', async () => {
94
- process.env.YNAB_ACCESS_TOKEN = 'instance-1-token';
95
- const server1 = new YNABMCPServer();
96
-
97
- process.env.YNAB_ACCESS_TOKEN = 'instance-2-token';
98
- const server2 = new YNABMCPServer();
99
-
100
- // Verify each server uses its instantiation-time config
101
- // Implementation detail: check via diagnostic_info or internal state
102
- expect(server1).toBeDefined();
103
- expect(server2).toBeDefined();
104
- });
105
- ```
106
- **Verification:** `npx vitest run src/server/__tests__/YNABMCPServer.test.ts`
107
- **Expected:** Fail initially (requires code change)
108
-
109
- **Step 2** (4 min): Modify YNABMCPServer constructor
110
- **File:** `src/server/YNABMCPServer.ts`
111
- **Action:** In constructor (around line 150-200):
112
- - Change from `import { config }` to using `loadConfig()`
113
- - Store `private readonly serverConfig: AppConfig`
114
- - Initialize as: `this.serverConfig = loadConfig();`
115
- - Use `this.serverConfig.YNAB_ACCESS_TOKEN` for YNAB API instantiation
116
- - Update all references from `config.YNAB_ACCESS_TOKEN` to `this.serverConfig.YNAB_ACCESS_TOKEN`
117
-
118
- **Step 3** (2 min): Re-run tests
119
- **Command:** `npx vitest run src/server/__tests__/YNABMCPServer.test.ts`
120
- **Expected:** Test from Step 1 now passes
121
-
122
- **Step 4** (2 min): Update integration tests
123
- **File:** `src/server/__tests__/server-startup.integration.test.ts`
124
- **Action:** Ensure tests expect `ValidationError` from `loadConfig()` failures
125
- **Verification:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
126
-
127
- ---
128
-
129
- ### **Task 1.3: Token Validation Resilience**
130
-
131
- **Goal:** Handle non-JSON YNAB responses gracefully (HTML error pages, malformed JSON).
132
-
133
- **Step 1** (3 min): Write failing test for non-JSON response
134
- **File:** `src/server/__tests__/server-startup.integration.test.ts`
135
- **Action:** Add test case:
136
- ```typescript
137
- it('throws AuthenticationError for malformed YNAB API responses', async () => {
138
- const mockAPI = {
139
- user: {
140
- getUser: vi.fn().mockRejectedValue(new SyntaxError('Unexpected token < in JSON'))
141
- }
142
- };
143
-
144
- await expect(validateToken(mockAPI as any))
145
- .rejects.toThrow(AuthenticationError);
146
- await expect(validateToken(mockAPI as any))
147
- .rejects.toThrow(/Unexpected response from YNAB/);
148
- });
149
- ```
150
- **Verification:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
151
- **Expected:** Fail (no try-catch for SyntaxError yet)
152
-
153
- **Step 2** (4 min): Implement graceful error handling
154
- **File:** `src/server/YNABMCPServer.ts`
155
- **Action:** In `validateToken()` method (locate via search for "validateToken"):
156
- ```typescript
157
- private async validateToken(api: ynab.API): Promise<void> {
158
- try {
159
- await api.user.getUser();
160
- } catch (error: unknown) {
161
- // Handle SyntaxError (malformed JSON), HTML responses, network errors
162
- if (error instanceof SyntaxError ||
163
- (error instanceof Error && error.message.includes('JSON'))) {
164
- throw new AuthenticationError(
165
- 'Unexpected response from YNAB during token validation. ' +
166
- 'Please verify your token is valid at https://app.youneedabudget.com/settings/developer'
167
- );
168
- }
169
-
170
- // Handle 401/403 as before
171
- if (isYNABError(error) && [401, 403].includes(error.status)) {
172
- throw new AuthenticationError(
173
- 'Invalid YNAB access token. Get a new token at: ' +
174
- 'https://app.youneedabudget.com/settings/developer'
175
- );
176
- }
177
-
178
- throw error; // Rethrow other errors
179
- }
180
- }
181
- ```
182
-
183
- **Step 3** (2 min): Re-run validation tests
184
- **Command:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
185
- **Expected:** All tests pass
186
-
187
- ---
188
-
189
- ### **Task 1.4: Windows Portability for Test Runner**
190
-
191
- **Step 1** (3 min): Fix throttled integration test runner for Windows
192
- **File:** `scripts/run-throttled-integration-tests.js`
193
- **Action:** Replace `.cmd` spawning with portable approach:
194
- ```javascript
195
- const { spawn } = require('child_process');
196
- const path = require('path');
197
-
198
- // Locate vitest binary portably
199
- const vitestBin = path.resolve(__dirname, '../node_modules/.bin/vitest');
200
- const vitestCmd = process.platform === 'win32' ? `${vitestBin}.cmd` : vitestBin;
201
-
202
- const proc = spawn('node', [vitestCmd, ...args], {
203
- stdio: 'inherit',
204
- shell: process.platform === 'win32'
205
- });
206
- ```
207
- **Verification:** `node scripts/run-throttled-integration-tests.js --help` (should not throw EINVAL)
208
-
209
- **Step 2** (1 min): Test script execution on Windows
210
- **Command:** `node scripts/run-throttled-integration-tests.js --help`
211
- **Expected:** Help output without spawn errors
212
-
213
- ---
214
-
215
- ### **Task 1.5: Full Verification**
216
-
217
- **Step 1** (3 min): Run unit test suite
218
- **Command:** `npm test`
219
- **Expected:** All 1334 tests pass (59 files)
220
-
221
- **Step 2** (5 min): Run core integration tests with valid token
222
- **Command:** `npm run test:integration:core` (requires `YNAB_ACCESS_TOKEN` in env)
223
- **Expected:** Integration tests pass or skip appropriately
224
-
225
- **Step 3** (2 min): Type-check codebase
226
- **Command:** `npm run type-check`
227
- **Expected:** 0 TypeScript errors
228
-
229
- **Step 4** (2 min): Commit changes
230
- **Commands:**
231
- ```bash
232
- git add src/server/config.ts src/server/YNABMCPServer.ts src/server/__tests__/*.ts scripts/run-throttled-integration-tests.js
233
- git commit -m "feat: reloadable config with per-instance isolation and robust token validation
234
-
235
- - Config loader now re-parses environment on each call for testability
236
- - YNABMCPServer uses per-instance config instead of module singleton
237
- - Token validation gracefully handles non-JSON YNAB responses
238
- - Throttled test runner portable to Windows (fixes spawn EINVAL)
239
- - Improves CI reliability and test isolation"
240
- ```
241
-
242
- ---
243
-
244
- ## **Priority 2: Reconciliation Error Handling**
245
-
246
- **Goal:** Propagate YNAB API errors (rate limits, invalid accounts, 404s) properly during reconciliation instead of silently returning zero creations.
247
-
248
- **Files Modified:**
249
- - `src/tools/reconciliation/executor.ts`
250
- - `src/tools/reconciliation/__tests__/executor.test.ts`
251
- - `src/tools/reconciliation/__tests__/executor.integration.test.ts`
252
-
253
- **Rationale:** Current executor catches all errors during bulk/sequential creation but doesn't distinguish fatal errors (404, 429) from transient ones. Tests expect failures but get silent 0-creation results.
254
-
255
- ---
256
-
257
- ### **Task 2.1: Add Error Normalization Utilities**
258
-
259
- **Step 1** (4 min): Write unit test for error normalization
260
- **File:** `src/tools/reconciliation/__tests__/executor.test.ts`
261
- **Action:** Add test cases:
262
- ```typescript
263
- describe('YNAB Error Normalization', () => {
264
- it('normalizes YNAB API error objects with status codes', () => {
265
- const ynabError = { error: { id: '429', detail: 'Too many requests' } };
266
- const normalized = normalizeYnabError(ynabError);
267
- expect(normalized.status).toBe(429);
268
- expect(normalized.message).toContain('Too many requests');
269
- });
270
-
271
- it('normalizes standard Error objects', () => {
272
- const error = new Error('Network failure');
273
- const normalized = normalizeYnabError(error);
274
- expect(normalized.message).toBe('Network failure');
275
- });
276
-
277
- it('identifies propagation-worthy errors', () => {
278
- expect(shouldPropagateYnabError({ status: 429 })).toBe(true);
279
- expect(shouldPropagateYnabError({ status: 404 })).toBe(true);
280
- expect(shouldPropagateYnabError({ status: 500 })).toBe(true);
281
- expect(shouldPropagateYnabError({ status: undefined })).toBe(false);
282
- });
283
- });
284
- ```
285
- **Verification:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "YNAB Error Normalization"`
286
- **Expected:** Fail (functions don't exist yet)
287
-
288
- **Step 2** (5 min): Implement error normalization functions
289
- **File:** `src/tools/reconciliation/executor.ts`
290
- **Action:** Add near helper section (before `executeReconciliation`):
291
- ```typescript
292
- interface NormalizedYnabError {
293
- status?: number;
294
- name?: string;
295
- message: string;
296
- detail?: string;
297
- }
298
-
299
- function normalizeYnabError(err: unknown): NormalizedYnabError {
300
- // YNAB SDK error format: { error: { id: '429', detail: '...', name: 'rate_limit' } }
301
- if (typeof err === 'object' && err !== null && 'error' in err) {
302
- const ynabErr = (err as any).error;
303
- return {
304
- status: parseInt(ynabErr.id) || undefined,
305
- name: ynabErr.name,
306
- message: ynabErr.detail || ynabErr.name || 'YNAB API error',
307
- detail: ynabErr.detail,
308
- };
309
- }
310
-
311
- // Standard Error object
312
- if (err instanceof Error) {
313
- return { message: err.message, name: err.name };
314
- }
315
-
316
- // String error
317
- if (typeof err === 'string') {
318
- return { message: err };
319
- }
320
-
321
- return { message: 'Unknown error' };
322
- }
323
-
324
- function shouldPropagateYnabError(err: NormalizedYnabError): boolean {
325
- // Fatal errors that should bubble up
326
- return [401, 403, 404, 429, 500, 503].includes(err.status ?? 0);
327
- }
328
-
329
- function attachStatus(err: NormalizedYnabError): Error {
330
- const error = new Error(err.message || err.detail || 'YNAB API error');
331
- if (err.status) (error as any).status = err.status;
332
- if (err.name) error.name = err.name;
333
- return error;
334
- }
335
- ```
336
-
337
- **Step 3** (2 min): Re-run unit tests
338
- **Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "YNAB Error Normalization"`
339
- **Expected:** All normalization tests pass
340
-
341
- ---
342
-
343
- ### **Task 2.2: Update Bulk Creation Error Handling**
344
-
345
- **Step 1** (3 min): Add test for bulk creation rate limit propagation
346
- **File:** `src/tools/reconciliation/__tests__/executor.integration.test.ts`
347
- **Action:** Add test:
348
- ```typescript
349
- it('propagates rate limit errors during bulk creation', async () => {
350
- mockCreateTransactions.rejects({
351
- error: { id: '429', detail: 'Too many requests', name: 'rate_limit' }
352
- });
353
-
354
- await expect(executeReconciliation({
355
- ynabAPI: mockAPI,
356
- analysis: mockAnalysisWithUnmatched,
357
- params: mockParams,
358
- budgetId: 'test-budget',
359
- accountId: 'test-account',
360
- initialAccount: mockAccount,
361
- currencyCode: 'USD',
362
- })).rejects.toMatchObject({ status: 429 });
363
- });
364
- ```
365
- **Verification:** `npx vitest run --project integration:core --testNamePattern="propagates rate limit"`
366
- **Expected:** Fail (currently catches all errors)
367
-
368
- **Step 2** (4 min): Modify bulk chunk error handling
369
- **File:** `src/tools/reconciliation/executor.ts`
370
- **Action:** In `processBulkChunk` catch block (search for "bulk_chunk_failures"):
371
- ```typescript
372
- catch (error) {
373
- const ynabErr = normalizeYnabError(error);
374
-
375
- // Propagate fatal errors
376
- if (shouldPropagateYnabError(ynabErr)) {
377
- throw attachStatus(ynabErr);
378
- }
379
-
380
- // Log non-fatal bulk failures and fall back to sequential
381
- const reason = ynabErr.message || 'Unknown error';
382
- bulkOperationDetails.bulk_chunk_failures += 1;
383
- actions_taken.push({
384
- type: 'bulk_create_fallback',
385
- reason: `Bulk chunk #${chunkIndex} failed (${reason}). Falling back to sequential creation.`
386
- });
387
- }
388
- ```
389
-
390
- **Step 3** (2 min): Re-run integration test
391
- **Command:** `npx vitest run --project integration:core --testNamePattern="propagates rate limit"`
392
- **Expected:** Test passes
393
-
394
- ---
395
-
396
- ### **Task 2.3: Update Sequential Creation Error Handling**
397
-
398
- **Step 1** (3 min): Add test for invalid account error
399
- **File:** `src/tools/reconciliation/__tests__/executor.test.ts`
400
- **Action:** Add test:
401
- ```typescript
402
- it('propagates invalid account errors during sequential creation', async () => {
403
- mockCreateTransaction.rejects({
404
- error: { id: '404', detail: 'Account not found', name: 'not_found' }
405
- });
406
-
407
- await expect(executeReconciliation({...})).rejects.toMatchObject({
408
- status: 404,
409
- message: expect.stringContaining('Account not found')
410
- });
411
- });
412
- ```
413
- **Verification:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "invalid account"`
414
- **Expected:** Fail
415
-
416
- **Step 2** (4 min): Modify sequential creation error handling
417
- **File:** `src/tools/reconciliation/executor.ts`
418
- **Action:** In sequential creation loop catch block (search for "create_transaction_failed"):
419
- ```typescript
420
- catch (error) {
421
- const ynabErr = normalizeYnabError(error);
422
- const failureReason = ynabErr.message || 'Unknown error';
423
-
424
- results.transaction_failures += 1;
425
- actions_taken.push({
426
- type: 'create_transaction_failed',
427
- bank_transaction_id: bankTx.id,
428
- reason: `Failed to create transaction: ${failureReason}${ynabErr.status ? ` (HTTP ${ynabErr.status})` : ''}`
429
- });
430
-
431
- // Propagate fatal errors after logging
432
- if (shouldPropagateYnabError(ynabErr)) {
433
- throw attachStatus(ynabErr);
434
- }
435
- }
436
- ```
437
-
438
- **Step 3** (2 min): Re-run test
439
- **Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "invalid account"`
440
- **Expected:** Pass
441
-
442
- ---
443
-
444
- ### **Task 2.4: Update Action Reason Logging**
445
-
446
- **Step 1** (3 min): Add test for rate limit detection in action log
447
- **File:** `src/tools/reconciliation/__tests__/executor.integration.test.ts`
448
- **Action:** Add helper and test:
449
- ```typescript
450
- function containsRateLimitFailure(actions: any[]): boolean {
451
- return actions.some(a =>
452
- a.reason?.includes('429') ||
453
- a.reason?.toLowerCase().includes('rate limit') ||
454
- a.reason?.includes('Too many requests')
455
- );
456
- }
457
-
458
- it('includes rate limit status in action reasons', async () => {
459
- mockCreateTransactions.rejects({ error: { id: '429', detail: 'Too many requests' } });
460
-
461
- try {
462
- await executeReconciliation({...});
463
- } catch (err) {
464
- // Error should propagate
465
- expect((err as any).status).toBe(429);
466
- }
467
-
468
- // If fallback occurred, actions should mention rate limit
469
- // (Test expectations depend on whether error propagates before logging)
470
- });
471
- ```
472
-
473
- **Step 2** (2 min): Verify action reasons include status codes
474
- **Action:** Review code from Task 2.2 Step 2 and Task 2.3 Step 2 to ensure:
475
- - Action reasons include HTTP status when available
476
- - Wording is consistent (`(HTTP 429)` format)
477
-
478
- **Step 3** (2 min): Run integration tests
479
- **Command:** `npm run test:integration:reconciliation` (if configured) or `npx vitest run --project integration --testPathPattern=reconciliation`
480
- **Expected:** Tests pass or skip on rate limits (not silent 0-creations)
481
-
482
- ---
483
-
484
- ### **Task 2.5: Full Verification**
485
-
486
- **Step 1** (3 min): Run reconciliation unit tests
487
- **Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts`
488
- **Expected:** All tests pass
489
-
490
- **Step 2** (5 min): Run reconciliation integration tests
491
- **Command:** `npm run test:integration:reconciliation` (requires YNAB token)
492
- **Expected:** Tests pass with proper error propagation
493
-
494
- **Step 3** (2 min): Commit changes
495
- **Commands:**
496
- ```bash
497
- git add src/tools/reconciliation/executor.ts src/tools/reconciliation/__tests__/executor*.test.ts
498
- git commit -m "fix: propagate fatal YNAB API errors during reconciliation
499
-
500
- - Add error normalization layer to distinguish fatal vs transient failures
501
- - Propagate 401/403/404/429/500/503 errors instead of silencing
502
- - Include HTTP status codes in action failure reasons
503
- - Improve rate limit detection in integration tests
504
- - Fixes silent 0-creation failures when account invalid or rate limited"
505
- ```
506
-
507
- ---
508
-
509
- ## **Priority 3: Fix Transaction Cached Property** ✅
510
-
511
- **Status:** ALREADY COMPLETED in v0.13.1 (per CHANGELOG.md line 48-51)
512
-
513
- **Evidence:**
514
- - CHANGELOG entry: "Fixed missing `cached` property in large transaction list responses (>90KB)"
515
- - Implementation details match plan in `docs/plans/2025-11-21-fix-transaction-cached-property.md`
516
-
517
- **No action required.** This can be removed from the planning queue.
518
-
519
- ---
520
-
521
- ## **Additional Quality Improvements (Optional)**
522
-
523
- These tasks can be tackled after Priorities 1-2:
524
-
525
- ### **Task 3.1: Improve Test Coverage Visibility**
526
-
527
- **Step 1** (2 min): Add coverage badge to README
528
- **File:** `README.md`
529
- **Action:** Add after existing badges:
530
- ```markdown
531
- [![Coverage](https://img.shields.io/badge/coverage-80%25-green.svg)](https://github.com/dizzlkheinz/ynab-mcpb)
532
- ```
533
-
534
- **Step 2** (3 min): Configure coverage thresholds in vitest config
535
- **File:** `vitest.config.ts` (search for coverage section)
536
- **Action:** Verify thresholds are enforced:
537
- ```typescript
538
- coverage: {
539
- thresholds: {
540
- branches: 80,
541
- functions: 80,
542
- lines: 80,
543
- statements: 80,
544
- },
545
- }
546
- ```
547
-
548
- ---
549
-
550
- ### **Task 3.2: Add Developer Quickstart Script**
551
-
552
- **Step 1** (3 min): Create dev setup script
553
- **File:** `scripts/dev-setup.sh` (new file)
554
- **Action:**
555
- ```bash
556
- #!/bin/bash
557
- # Quick setup for new developers
558
-
559
- echo "YNAB MCP Server - Developer Setup"
560
- echo "=================================="
561
-
562
- # Check Node version
563
- node -v | grep -q "v1[89]" || node -v | grep -q "v2[0-9]" || {
564
- echo "❌ Node.js 18+ required"
565
- exit 1
566
- }
567
-
568
- # Install dependencies
569
- npm install
570
-
571
- # Prompt for YNAB token
572
- echo ""
573
- echo "Get your YNAB Access Token from:"
574
- echo "https://app.youneedabudget.com/settings/developer"
575
- echo ""
576
- read -p "Enter YNAB_ACCESS_TOKEN: " token
577
-
578
- # Create .env file
579
- echo "YNAB_ACCESS_TOKEN=$token" > .env
580
-
581
- # Run tests
582
- echo ""
583
- echo "Running unit tests..."
584
- npm test
585
-
586
- echo ""
587
- echo "✅ Setup complete! Try: npm run build"
588
- ```
589
-
590
- **Step 2** (1 min): Make executable
591
- **Command:** `chmod +x scripts/dev-setup.sh` (or Windows equivalent)
592
-
593
- **Step 3** (1 min): Document in README
594
- **File:** `README.md`
595
- **Action:** Add to developer section:
596
- ```markdown
597
- ### Developer Setup
598
-
599
- ```bash
600
- npm install
601
- ./scripts/dev-setup.sh # Guided setup with token prompt
602
- npm run build
603
- npm test
604
- ```
605
-
606
- ---
607
-
608
- ## **Testing Strategy Summary**
609
-
610
- ### **Unit Tests**
611
- - **Target:** 80%+ coverage (already achieved: 1334 tests passing)
612
- - **Fast feedback:** < 15 seconds for full suite
613
- - **Mocked dependencies:** YNAB API, file system, network
614
-
615
- ### **Integration Tests**
616
- - **Core suite:** `test:integration:core` (mocked YNAB API)
617
- - **Domain suites:** `test:integration:domain` (budgets, accounts, transactions, etc.)
618
- - **Rate-limited:** Throttled runner prevents 429s in CI
619
- - **Timeouts:** 90 minutes for full suite (YNAB API 200/hour limit)
620
-
621
- ### **E2E Tests**
622
- - **Requires:** Real YNAB_ACCESS_TOKEN
623
- - **Use case:** Pre-release verification
624
- - **Frequency:** Manual or release workflow only
625
-
626
- ---
627
-
628
- ## **Rollout Strategy**
629
-
630
- ### **Phase 1: Priority 1 (Config & Token Validation)**
631
- **Duration:** ~45 minutes of focused work
632
- **Release:** v0.14.0-alpha.1
633
- **Testing:** Unit tests + core integration tests
634
-
635
- ### **Phase 2: Priority 2 (Reconciliation Errors)**
636
- **Duration:** ~40 minutes of focused work
637
- **Release:** v0.14.0-alpha.2
638
- **Testing:** Reconciliation integration tests (may hit rate limits)
639
-
640
- ### **Phase 3: Quality Improvements (Optional)**
641
- **Duration:** ~20 minutes
642
- **Release:** v0.14.0-beta.1
643
- **Testing:** Full test suite + coverage report
644
-
645
- ### **Phase 4: Production Release**
646
- **Duration:** ~10 minutes (docs, CHANGELOG, tagging)
647
- **Release:** v0.14.0
648
- **Testing:** E2E tests with real YNAB token
649
- **Deployment:** npm publish + GitHub release with MCPB file
650
-
651
- ---
652
-
653
- ## **Success Criteria**
654
-
655
- - ✅ All 1334+ unit tests pass
656
- - ✅ Integration tests pass or skip gracefully (no silent failures)
657
- - ✅ TypeScript strict mode: 0 errors
658
- - ✅ Config is reloadable per-instance (enables better testing)
659
- - ✅ Token validation handles malformed YNAB responses
660
- - ✅ Reconciliation propagates fatal errors (404, 429, 500)
661
- - ✅ Windows CI compatibility (no spawn EINVAL)
662
- - ✅ CHANGELOG updated with breaking changes (if any)
663
- - ✅ Semantic versioning: v0.14.0 (minor bump for new features)
664
-
665
- ---
666
-
667
- ## **Breaking Changes Assessment**
668
-
669
- **None expected.** All changes are:
670
- - Internal refactoring (config injection)
671
- - Improved error handling (stricter but more correct)
672
- - Bug fixes (reconciliation error propagation)
673
-
674
- **API compatibility:** 100% backward compatible with v0.13.x
675
-
676
- ---
677
-
678
- ## **Documentation Updates Required**
679
-
680
- 1. **CHANGELOG.md** - Add v0.14.0 section with:
681
- - "Reloadable config for improved testability"
682
- - "Robust token validation for non-JSON responses"
683
- - "Reconciliation error propagation (404/429/500)"
684
- - "Windows CI compatibility fix"
685
-
686
- 2. **docs/guides/TESTING.md** - Add section on:
687
- - Per-instance config testing patterns
688
- - Error normalization in reconciliation tests
689
-
690
- 3. **README.md** (optional):
691
- - Update badges if coverage changes
692
- - Add developer quickstart script reference
693
-
694
- ---
695
-
696
- ## **Risk Mitigation**
697
-
698
- | Risk | Mitigation |
699
- |------|-----------|
700
- | **Config changes break production** | Singleton `config` export preserved for backward compatibility |
701
- | **Token validation too strict** | Only catch SyntaxError/JSON errors, rethrow others |
702
- | **Reconciliation breaks existing workflows** | Only propagate 401/403/404/429/500/503, other errors still fall back |
703
- | **Windows test runner fails** | Portable node/vitest spawning, tested on Windows before commit |
704
- | **Rate limits block CI** | Integration tests already optional with `continue-on-error` |
705
-
706
- ---
707
-
708
- ## **Post-Implementation Checklist**
709
-
710
- - [ ] All unit tests pass (`npm test`)
711
- - [ ] All integration tests pass or skip (`npm run test:integration:core`)
712
- - [ ] Type check passes (`npm run type-check`)
713
- - [ ] Linter passes (`npm run lint`)
714
- - [ ] Build succeeds (`npm run build`)
715
- - [ ] CHANGELOG.md updated
716
- - [ ] Git commit messages follow conventional commits
717
- - [ ] GitHub Actions CI passes
718
- - [ ] Manual smoke test with real YNAB token
719
- - [ ] Version bump to 0.14.0 in package.json
720
- - [ ] Git tag created: `v0.14.0`
721
- - [ ] npm publish successful
722
- - [ ] GitHub release created with MCPB attachment
723
-
724
- ---
725
-
726
- ## **Next Steps Beyond v0.14.0**
727
-
728
- **Future enhancements identified during planning:**
729
-
730
- 1. **Tool-level timeout configuration** - Allow per-tool timeout overrides for long-running operations
731
- 2. **Reconciliation insights engine** - ML-based payee matching suggestions
732
- 3. **Cached delta optimization** - Reduce redundant fetches for unchanged budgets
733
- 4. **Multi-budget batch operations** - Parallel budget queries with Promise.allSettled
734
- 5. **Output schema versioning** - Support schema evolution without breaking changes
735
-
736
- ---
737
-
738
- **End of Implementation Plan**
739
-
740
- This plan provides a concrete roadmap for the next iteration with:
741
- - **Exact file paths** for every change
742
- - **2-5 minute tasks** following TDD methodology
743
- - **Verification commands** after each step
744
- - **Rollback-safe approach** (all changes testable before commit)
745
- - **Clear success criteria** and risk mitigation
746
-
747
- Estimated total implementation time: **2-3 hours** for Priorities 1-2, excluding CI wait times.