@dizzlkheinz/ynab-mcpb 0.12.2 → 0.15.0

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 (262) hide show
  1. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
  2. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
  3. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
  4. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
  5. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
  6. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
  7. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
  8. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
  9. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
  10. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
  11. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
  12. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
  13. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
  14. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
  15. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
  16. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
  17. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
  18. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
  19. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
  20. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
  21. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
  22. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
  23. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
  24. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
  25. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
  26. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
  27. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
  28. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
  29. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
  30. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
  31. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
  32. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
  33. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
  34. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
  35. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
  36. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
  37. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
  38. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
  39. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
  40. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
  41. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
  42. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
  43. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
  44. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
  45. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
  46. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
  47. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
  48. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
  49. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
  50. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
  51. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
  52. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
  53. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
  54. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
  55. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
  56. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
  57. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
  58. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
  59. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
  60. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
  61. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
  62. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
  63. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
  64. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
  65. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
  66. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
  67. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
  68. package/.github/workflows/ci-tests.yml +6 -2
  69. package/.github/workflows/publish.yml +3 -3
  70. package/.github/workflows/release.yml +4 -0
  71. package/CHANGELOG.md +89 -1
  72. package/NUL +1 -1
  73. package/README.md +36 -10
  74. package/dist/bundle/index.cjs +65 -42
  75. package/dist/index.js +9 -20
  76. package/dist/server/YNABMCPServer.d.ts +2 -1
  77. package/dist/server/YNABMCPServer.js +61 -27
  78. package/dist/server/cacheKeys.d.ts +8 -0
  79. package/dist/server/cacheKeys.js +8 -0
  80. package/dist/server/config.d.ts +22 -3
  81. package/dist/server/config.js +16 -17
  82. package/dist/server/errorHandler.d.ts +2 -0
  83. package/dist/server/errorHandler.js +49 -5
  84. package/dist/server/securityMiddleware.js +3 -6
  85. package/dist/server/toolRegistry.js +8 -10
  86. package/dist/tools/accountTools.js +4 -3
  87. package/dist/tools/categoryTools.js +8 -7
  88. package/dist/tools/monthTools.js +2 -1
  89. package/dist/tools/payeeTools.js +2 -1
  90. package/dist/tools/reconcileAdapter.js +10 -5
  91. package/dist/tools/reconciliation/analyzer.d.ts +4 -2
  92. package/dist/tools/reconciliation/analyzer.js +120 -404
  93. package/dist/tools/reconciliation/csvParser.d.ts +51 -0
  94. package/dist/tools/reconciliation/csvParser.js +413 -0
  95. package/dist/tools/reconciliation/executor.d.ts +8 -0
  96. package/dist/tools/reconciliation/executor.js +277 -50
  97. package/dist/tools/reconciliation/index.d.ts +7 -7
  98. package/dist/tools/reconciliation/index.js +115 -39
  99. package/dist/tools/reconciliation/matcher.d.ts +24 -3
  100. package/dist/tools/reconciliation/matcher.js +175 -133
  101. package/dist/tools/reconciliation/recommendationEngine.js +22 -18
  102. package/dist/tools/reconciliation/reportFormatter.js +9 -8
  103. package/dist/tools/reconciliation/signDetector.d.ts +2 -0
  104. package/dist/tools/reconciliation/signDetector.js +54 -0
  105. package/dist/tools/reconciliation/types.d.ts +20 -34
  106. package/dist/tools/reconciliation/types.js +1 -7
  107. package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
  108. package/dist/tools/reconciliation/ynabAdapter.js +15 -0
  109. package/dist/tools/transactionTools.d.ts +3 -17
  110. package/dist/tools/transactionTools.js +5 -17
  111. package/dist/types/reconciliation.d.ts +24 -0
  112. package/dist/types/reconciliation.js +1 -0
  113. package/dist/utils/baseError.d.ts +3 -0
  114. package/dist/utils/baseError.js +7 -0
  115. package/dist/utils/errors.d.ts +13 -0
  116. package/dist/utils/errors.js +15 -0
  117. package/dist/utils/validationError.d.ts +3 -0
  118. package/dist/utils/validationError.js +3 -0
  119. package/docs/guides/ARCHITECTURE.md +12 -129
  120. package/docs/plans/2025-11-20-reloadable-config-token-validation.md +93 -0
  121. package/docs/plans/2025-11-21-fix-transaction-cached-property.md +362 -0
  122. package/docs/plans/2025-11-21-reconciliation-error-handling.md +90 -0
  123. package/docs/plans/2025-11-21-v014-hardening.md +153 -0
  124. package/docs/plans/reconciliation-v2-redesign.md +1571 -0
  125. package/package.json +8 -2
  126. package/scripts/run-throttled-integration-tests.js +9 -3
  127. package/scripts/test-recommendations.ts +1 -1
  128. package/src/__tests__/performance.test.ts +12 -5
  129. package/src/__tests__/testUtils.ts +62 -5
  130. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
  131. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
  132. package/src/__tests__/workflows.e2e.test.ts +33 -0
  133. package/src/index.ts +8 -31
  134. package/src/server/YNABMCPServer.ts +81 -42
  135. package/src/server/__tests__/YNABMCPServer.integration.test.ts +10 -12
  136. package/src/server/__tests__/YNABMCPServer.test.ts +27 -15
  137. package/src/server/__tests__/config.test.ts +76 -152
  138. package/src/server/__tests__/server-startup.integration.test.ts +42 -14
  139. package/src/server/__tests__/toolRegistry.test.ts +1 -1
  140. package/src/server/cacheKeys.ts +8 -0
  141. package/src/server/config.ts +20 -38
  142. package/src/server/errorHandler.ts +52 -5
  143. package/src/server/securityMiddleware.ts +3 -7
  144. package/src/server/toolRegistry.ts +14 -10
  145. package/src/tools/__tests__/categoryTools.test.ts +37 -19
  146. package/src/tools/__tests__/transactionTools.test.ts +58 -2
  147. package/src/tools/accountTools.ts +8 -3
  148. package/src/tools/categoryTools.ts +12 -7
  149. package/src/tools/monthTools.ts +7 -1
  150. package/src/tools/payeeTools.ts +7 -1
  151. package/src/tools/reconcileAdapter.ts +10 -5
  152. package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
  153. package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
  154. package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
  155. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +26 -6
  156. package/src/tools/reconciliation/__tests__/executor.test.ts +133 -60
  157. package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
  158. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
  159. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
  160. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
  161. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
  162. package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
  163. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
  164. package/src/tools/reconciliation/analyzer.ts +174 -545
  165. package/src/tools/reconciliation/csvParser.ts +617 -0
  166. package/src/tools/reconciliation/executor.ts +344 -58
  167. package/src/tools/reconciliation/index.ts +141 -48
  168. package/src/tools/reconciliation/matcher.ts +234 -214
  169. package/src/tools/reconciliation/recommendationEngine.ts +23 -19
  170. package/src/tools/reconciliation/reportFormatter.ts +16 -11
  171. package/src/tools/reconciliation/signDetector.ts +117 -0
  172. package/src/tools/reconciliation/types.ts +39 -61
  173. package/src/tools/reconciliation/ynabAdapter.ts +33 -0
  174. package/src/tools/schemas/outputs/utilityOutputs.ts +1 -1
  175. package/src/tools/transactionTools.ts +7 -18
  176. package/src/types/reconciliation.ts +49 -0
  177. package/src/utils/baseError.ts +7 -0
  178. package/src/utils/errors.ts +21 -0
  179. package/src/utils/validationError.ts +3 -0
  180. package/temp-recon.ts +126 -0
  181. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
  182. package/test_mcp_tools.mjs +75 -0
  183. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
  184. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
  185. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
  186. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
  187. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
  188. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
  189. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
  190. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
  191. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
  192. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
  193. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
  194. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
  195. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
  196. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
  197. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
  198. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
  199. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
  200. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
  201. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
  202. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
  203. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
  204. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
  205. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
  206. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
  207. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
  208. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
  209. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
  210. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
  211. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
  212. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
  213. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
  214. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
  215. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
  216. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
  217. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
  218. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
  219. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
  220. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
  221. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
  222. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
  223. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
  224. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
  225. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
  226. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
  227. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
  228. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
  229. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
  230. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
  231. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
  232. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
  233. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
  234. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
  235. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
  236. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
  237. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
  238. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
  239. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
  240. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
  241. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
  242. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
  243. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
  244. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
  245. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
  246. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
  247. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
  248. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
  249. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
  250. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
  251. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
  252. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
  253. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
  254. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
  255. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
  256. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
  257. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
  258. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
  259. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
  260. package/.gemini/settings.json +0 -8
  261. package/ADOS-2-Module-1-Complete-Manual.md +0 -757
  262. package/WARP.md +0 -245
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dizzlkheinz/ynab-mcpb",
3
- "version": "0.12.2",
3
+ "version": "0.15.0",
4
4
  "description": "Model Context Protocol server for YNAB (You Need A Budget) integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -67,17 +67,23 @@
67
67
  "license": "AGPL-3.0",
68
68
  "dependencies": {
69
69
  "@modelcontextprotocol/sdk": "^1.22.0",
70
+ "chrono-node": "^2.9.0",
70
71
  "csv-parse": "^6.1.0",
71
72
  "d3-array": "^3.2.4",
72
73
  "date-fns": "^4.1.0",
74
+ "dayjs": "^1.11.19",
73
75
  "dotenv": "^17.2.1",
76
+ "fuzzball": "^2.2.3",
77
+ "papaparse": "^5.5.3",
74
78
  "ynab": "^2.9.0",
75
- "zod": "^4.1.11"
79
+ "zod": "^4.1.11",
80
+ "zod-validation-error": "^5.0.0"
76
81
  },
77
82
  "devDependencies": {
78
83
  "@eslint/js": "^9.35.0",
79
84
  "@types/d3-array": "^3.2.1",
80
85
  "@types/node": "^24.5.2",
86
+ "@types/papaparse": "^5.5.0",
81
87
  "@vitest/coverage-v8": "^3.2.4",
82
88
  "@vitest/ui": "^3.2.4",
83
89
  "esbuild": "^0.25.10",
@@ -91,10 +91,16 @@ function estimateCalls(filePath) {
91
91
 
92
92
  async function runVitestFile(testFile) {
93
93
  const normalized = toPosixPath(testFile);
94
- const vitestArgs = ['vitest', 'run', '--project', 'integration:full', normalized];
95
- const runner = process.platform === 'win32' ? 'npx.cmd' : 'npx';
96
- const child = spawn(runner, vitestArgs, {
94
+ const vitestBin = path.join(
95
+ projectRoot,
96
+ 'node_modules',
97
+ '.bin',
98
+ process.platform === 'win32' ? 'vitest.cmd' : 'vitest',
99
+ );
100
+ const vitestArgs = ['run', '--project', 'integration:full', normalized];
101
+ const child = spawn(vitestBin, vitestArgs, {
97
102
  stdio: 'inherit',
103
+ shell: process.platform === 'win32',
98
104
  env: {
99
105
  ...process.env,
100
106
  INTEGRATION_TEST_TIER: 'full',
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { analyzeReconciliation } from '../src/tools/reconciliation/analyzer.js';
7
- import { DEFAULT_MATCHING_CONFIG } from '../src/tools/reconciliation/types.js';
7
+ import { DEFAULT_CONFIG as DEFAULT_MATCHING_CONFIG } from '../src/tools/reconciliation/matcher.js';
8
8
 
9
9
  // Test data from user's scenario
10
10
  const csvContent = `Date,Description,Amount
@@ -9,6 +9,7 @@ import { executeReconciliation, type AccountSnapshot } from '../tools/reconcilia
9
9
  import type { ReconciliationAnalysis } from '../tools/reconciliation/types.js';
10
10
  import type { ReconcileAccountRequest } from '../tools/reconciliation/index.js';
11
11
  import type * as ynab from 'ynab';
12
+ import { SecurityErrorCode } from '../server/errorHandler.js';
12
13
 
13
14
  /**
14
15
  * Helper function to validate tool responses and extract array data
@@ -26,7 +27,8 @@ function validateToolResponse<T>(result: any, fieldSelector: (data: any) => T[]
26
27
  const hasError = parsed.error || parsed.data?.error;
27
28
  if (hasError) {
28
29
  throw new Error(
29
- `Tool returned error: ${JSON.stringify(hasError, null, 2)}\nFull response: ${JSON.stringify(parsed, null, 2)}`,
30
+ `Tool returned error: ${JSON.stringify(hasError, null, 2)}
31
+ Full response: ${JSON.stringify(parsed, null, 2)}`,
30
32
  );
31
33
  }
32
34
 
@@ -375,11 +377,16 @@ describe('YNAB MCP Server - Performance Tests', () => {
375
377
  let mockYnabAPI: any;
376
378
 
377
379
  beforeEach(async () => {
378
- process.env['YNAB_ACCESS_TOKEN'] = 'test-token';
380
+ // Ensure YNAB_ACCESS_TOKEN is set for all tests, even if just a placeholder
381
+ process.env['YNAB_ACCESS_TOKEN'] = 'test-token-performance';
382
+ // Clear modules to ensure fresh import of server with new env var
383
+ vi.resetModules();
384
+ const { YNABMCPServer } = await import('../server/YNABMCPServer.js');
379
385
  server = new YNABMCPServer();
380
386
 
387
+ // Mock the YNAB API constructor to ensure it receives the correct access token
381
388
  const { API } = await import('ynab');
382
- mockYnabAPI = new (API as any)();
389
+ mockYnabAPI = new (API as any)('test-token-performance');
383
390
 
384
391
  vi.clearAllMocks();
385
392
  // Clear cache to ensure mocks are called in each test
@@ -645,8 +652,8 @@ describe('YNAB MCP Server - Performance Tests', () => {
645
652
  expect(parsed[0]).toBeDefined(); // Valid call should succeed
646
653
  const firstError = parsed[1].error ?? parsed[1].data?.error;
647
654
  const secondError = parsed[2].error ?? parsed[2].data?.error;
648
- expect(firstError?.code).toBe('VALIDATION_ERROR'); // Invalid calls should fail
649
- expect(secondError?.code).toBe('VALIDATION_ERROR');
655
+ expect(firstError?.code).toBe(SecurityErrorCode.VALIDATION_ERROR); // Invalid calls should fail
656
+ expect(secondError?.code).toBe(SecurityErrorCode.VALIDATION_ERROR);
650
657
  expect(totalTime).toBeLessThan(1000); // Validation should be fast
651
658
  });
652
659
  });
@@ -499,12 +499,19 @@ export function isRateLimitError(error: any): boolean {
499
499
  // Check for HTML responses (YNAB API returns HTML when rate limited or down)
500
500
  // This manifests as JSON parsing errors with messages like:
501
501
  // "SyntaxError: Unexpected token '<', "<style>..." is not valid JSON"
502
+ const looksLikeHTML =
503
+ errorString.includes('<html') ||
504
+ errorString.includes('<head') ||
505
+ errorString.includes('<body') ||
506
+ errorString.includes('<!doctype html');
507
+
502
508
  const isHTMLResponse =
503
- (errorString.includes('syntaxerror') || errorString.includes('unexpected token')) &&
504
- (errorString.includes("'<'") ||
505
- errorString.includes('"<"') ||
506
- errorString.includes('<style') ||
507
- errorString.includes('not valid json'));
509
+ looksLikeHTML ||
510
+ ((errorString.includes('syntaxerror') || errorString.includes('unexpected token')) &&
511
+ (errorString.includes("'<'") ||
512
+ errorString.includes('"<"') ||
513
+ errorString.includes('<style') ||
514
+ errorString.includes('not valid json')));
508
515
 
509
516
  // Check for VALIDATION_ERROR from output schema validation failures
510
517
  // These occur when YNAB API returns error responses instead of data during rate limiting
@@ -531,6 +538,56 @@ export function isRateLimitError(error: any): boolean {
531
538
  return hasRateLimitMessage || isHTMLResponse || isValidationError;
532
539
  }
533
540
 
541
+ /**
542
+ * Detects rate limit responses that are embedded in a CallToolResult (text JSON with an error object).
543
+ * Returns true and optionally skips the current test when a rate limit is found.
544
+ */
545
+ export function skipIfRateLimitedResult(
546
+ result: CallToolResult,
547
+ context?: { skip?: () => void },
548
+ ): boolean {
549
+ const markSkipped = () => {
550
+ console.warn('[rate-limit] Skipping test due to YNAB API rate limit (embedded payload)');
551
+ context?.skip?.();
552
+ };
553
+
554
+ const content = result.content?.[0];
555
+ const text = content && content.type === 'text' ? content.text : '';
556
+
557
+ try {
558
+ const parsed = typeof text === 'string' && text.trim().length > 0 ? JSON.parse(text) : null;
559
+ const candidates: any[] = [];
560
+
561
+ if (parsed && typeof parsed === 'object') {
562
+ const parsedObj = parsed as Record<string, unknown>;
563
+ if ('error' in parsedObj) candidates.push(parsedObj['error']);
564
+ if ('data' in parsedObj) {
565
+ const data = (parsedObj as any).data;
566
+ candidates.push(data?.error ?? data);
567
+ }
568
+ candidates.push(parsed);
569
+ }
570
+
571
+ if (typeof text === 'string') {
572
+ candidates.push(text);
573
+ }
574
+
575
+ for (const candidate of candidates) {
576
+ if (isRateLimitError(candidate)) {
577
+ markSkipped();
578
+ return true;
579
+ }
580
+ }
581
+ } catch (parseError) {
582
+ if (isRateLimitError(parseError) || isRateLimitError(text)) {
583
+ markSkipped();
584
+ return true;
585
+ }
586
+ // If parsing fails and no rate limit markers are present, fall through.
587
+ }
588
+ return false;
589
+ }
590
+
534
591
  /**
535
592
  * Runs a test function and skips the test if a YNAB API rate limit error occurs.
536
593
  *
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCSV } from '../../../tools/reconciliation/csvParser.js';
3
+ import { findMatches } from '../../../tools/reconciliation/matcher.js';
4
+ import { normalizeYNABTransaction } from '../../../tools/reconciliation/ynabAdapter.js';
5
+
6
+ describe('CSV Parser Integration Tests', () => {
7
+ describe('TD Bank CSV', () => {
8
+ const tdCSV = `Date,Description,Amount
9
+ 09/15/2025,SHELL STATION 1234 TORONTO ON,-45.23
10
+ 09/16/2025,AMZN MKTP CA*1A2B3C4,-127.99
11
+ 09/17/2025,PAYROLL DEPOSIT ABC CORP,2500.00`;
12
+
13
+ it('should parse TD CSV correctly', () => {
14
+ const result = parseCSV(tdCSV, { preset: 'td' });
15
+
16
+ expect(result.errors).toHaveLength(0);
17
+ expect(result.transactions).toHaveLength(3);
18
+ expect(result.transactions[0].amount).toBe(-45230); // Milliunits!
19
+ expect(result.transactions[0].payee).toBe('SHELL STATION 1234 TORONTO ON');
20
+ expect(result.transactions[0].date).toBe('2025-09-15');
21
+ });
22
+ });
23
+
24
+ describe('RBC Debit/Credit CSV', () => {
25
+ const rbcCSV = `Transaction Date,Description 1,Debit,Credit
26
+ 2025-09-15,SHELL GAS,45.23,
27
+ 2025-09-16,TRANSFER FROM SAVINGS,,500.00`;
28
+
29
+ it('should parse RBC CSV with debit/credit columns', () => {
30
+ const result = parseCSV(rbcCSV, { preset: 'rbc' });
31
+
32
+ expect(result.errors).toHaveLength(0);
33
+ expect(result.transactions).toHaveLength(2);
34
+ expect(result.transactions[0].amount).toBe(-45230); // Debit = negative milliunits
35
+ expect(result.transactions[1].amount).toBe(500000); // Credit = positive milliunits
36
+ });
37
+ });
38
+
39
+ describe('Ambiguous Debit/Credit Warning', () => {
40
+ const ambiguousCSV = `Transaction Date,Description,Debit,Credit
41
+ 2025-09-15,WEIRD TXN,50.00,25.00`;
42
+
43
+ it('should warn when both debit and credit have values', () => {
44
+ const result = parseCSV(ambiguousCSV, { preset: 'rbc' });
45
+
46
+ expect(result.warnings).toHaveLength(1);
47
+ expect(result.warnings[0].message).toContain('Both Debit');
48
+ expect(result.transactions[0].amount).toBe(-50000); // Uses debit
49
+ });
50
+ });
51
+
52
+ describe('European Number Format', () => {
53
+ const euroCSV = `Date,Amount,Description
54
+ 15/09/2025,"1.234,56",Big Purchase`;
55
+
56
+ it('should handle European number format', () => {
57
+ const result = parseCSV(euroCSV);
58
+
59
+ expect(result.transactions[0].amount).toBe(1234560); // 1234.56 in milliunits
60
+ });
61
+ });
62
+ });
63
+
64
+ describe('Matcher Integration Tests', () => {
65
+ const mockYNABTransactions = [
66
+ {
67
+ id: 'y1',
68
+ date: '2025-09-15',
69
+ amount: -45230,
70
+ payee_name: 'Shell',
71
+ category_name: 'Gas',
72
+ cleared: 'uncleared',
73
+ approved: true,
74
+ },
75
+ {
76
+ id: 'y2',
77
+ date: '2025-09-17',
78
+ amount: -127990,
79
+ payee_name: 'Amazon',
80
+ category_name: 'Shopping',
81
+ cleared: 'uncleared',
82
+ approved: true,
83
+ },
84
+ ].map((t) => normalizeYNABTransaction(t as any));
85
+
86
+ it('should achieve high confidence matches with exact integer comparison', () => {
87
+ const bankCSV = `Date,Description,Amount
88
+ 09/15/2025,SHELL STATION 1234,-45.23
89
+ 09/16/2025,AMZN MKTP CA*ABC123,-127.99`;
90
+
91
+ const parsed = parseCSV(bankCSV);
92
+ const matches = findMatches(parsed.transactions, mockYNABTransactions);
93
+
94
+ // Shell: exact amount match (both -45230 milliunits)
95
+ expect(matches[0].confidence).toBe('high');
96
+ expect(matches[0].bestMatch?.scores.amount).toBe(100);
97
+
98
+ // Amazon: exact amount match (both -127990 milliunits)
99
+ expect(matches[1].confidence).toBe('high');
100
+ expect(matches[1].bestMatch?.scores.amount).toBe(100);
101
+ });
102
+
103
+ it('should use exact integer comparison (no float precision issues)', () => {
104
+ // Both are now integers - no floating point comparison needed!
105
+ const bankTxn = {
106
+ id: 'b1',
107
+ date: '2025-09-15',
108
+ amount: -45230, // Integer milliunits
109
+ payee: 'Shell',
110
+ sourceRow: 2,
111
+ raw: { date: '09/15/2025', amount: '-45.23', description: 'Shell' },
112
+ };
113
+
114
+ const ynabTxn = {
115
+ id: 'y1',
116
+ date: '2025-09-15',
117
+ amount: -45230, // Integer milliunits - direct from YNAB API
118
+ payee: 'Shell',
119
+ memo: null,
120
+ categoryName: 'Gas',
121
+ cleared: 'uncleared' as const,
122
+ approved: true,
123
+ };
124
+
125
+ const matches = findMatches([bankTxn], [ynabTxn]);
126
+ // Exact match because integers compare exactly: -45230 === -45230
127
+ expect(matches[0].bestMatch?.scores.amount).toBe(100);
128
+ });
129
+ });
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCSV } from '../../../tools/reconciliation/csvParser.js';
3
+
4
+ describe('Real World CSV Validation (Simulated)', () => {
5
+ describe('Wealthsimple', () => {
6
+ // Simulated content based on real structure
7
+ const wsContent = `Date,Payee,Amount
8
+ 2025-11-21,"Amazon.Ca*B03Vv2Ss0",-42.68
9
+ 2025-11-21,"Amazon.Ca*B06I19Si0",-33.56
10
+ 2025-11-21,"Amzn Mktp Ca*B07U50Um2",-37.79`;
11
+
12
+ it('should parse Wealthsimple export correctly', () => {
13
+ const result = parseCSV(wsContent);
14
+
15
+ expect(result.errors).toHaveLength(0);
16
+ expect(result.transactions.length).toBe(3);
17
+
18
+ const first = result.transactions[0];
19
+ expect(first.date).toBe('2025-11-21');
20
+ expect(first.payee).toContain('Amazon');
21
+ expect(first.amount).toBe(-42680); // -42.68
22
+ });
23
+ });
24
+
25
+ describe('TD Canada Trust', () => {
26
+ // Simulated content based on real structure (Headerless)
27
+ // Date, Desc, Debit, Credit, Balance
28
+ const tdContent = `11/21/2025,EvoCarShare,23.87,,132.91
29
+ 11/19/2025,KOODO MOBILE PAC,43.68,,109.04
30
+ 11/14/2025,PAYMENT - THANK YOU,,1378.31,0.00`;
31
+
32
+ it('should parse TD export correctly using preset', () => {
33
+ // TD export is headerless
34
+ // We expect the 'td' preset to handle header: false and index mappings
35
+ const result = parseCSV(tdContent, { preset: 'td' });
36
+
37
+ expect(result.errors).toHaveLength(0);
38
+ expect(result.transactions.length).toBe(3);
39
+
40
+ const first = result.transactions[0];
41
+ // 11/21/2025
42
+ expect(first.date).toBe('2025-11-21');
43
+ expect(first.payee).toBe('EvoCarShare');
44
+ // 23.87 Debit = Outflow = Negative
45
+ expect(first.amount).toBe(-23870);
46
+
47
+ const third = result.transactions[2];
48
+ // 1378.31 Credit = Inflow = Positive
49
+ expect(third.payee).toBe('PAYMENT - THANK YOU');
50
+ expect(third.amount).toBe(1378310);
51
+ });
52
+ });
53
+ });
@@ -13,6 +13,7 @@ import {
13
13
  parseToolResult,
14
14
  isErrorResult,
15
15
  getErrorMessage,
16
+ skipIfRateLimitedResult,
16
17
  TestData,
17
18
  TestDataCleanup,
18
19
  YNABAssertions,
@@ -227,6 +228,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
227
228
  type: 'checking',
228
229
  balance: 10000, // $10.00
229
230
  });
231
+ if (skipIfRateLimitedResult(createResult)) return;
230
232
 
231
233
  // Validate output schema
232
234
  const createValidation = validateOutputSchema(server, 'create_account', createResult);
@@ -250,6 +252,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
250
252
  const accountsResult = await executeToolCall(server, 'ynab:list_accounts', {
251
253
  budget_id: testBudgetId,
252
254
  });
255
+ if (skipIfRateLimitedResult(accountsResult)) return;
253
256
  const accounts = parseToolResult(accountsResult);
254
257
 
255
258
  const foundAccount = accounts.data.accounts.find(
@@ -292,6 +295,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
292
295
  budget_id: testBudgetId,
293
296
  ...transactionData,
294
297
  });
298
+ if (skipIfRateLimitedResult(createResult)) return;
295
299
 
296
300
  // Validate create_transaction output schema
297
301
  const createValidation = validateOutputSchema(server, 'create_transaction', createResult);
@@ -302,6 +306,13 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
302
306
 
303
307
  const createdTransaction = parseToolResult(createResult);
304
308
 
309
+ if (!createdTransaction?.data?.transaction) {
310
+ console.warn(
311
+ '[rate-limit] Skipping transaction workflow because create_transaction returned no transaction data',
312
+ );
313
+ return;
314
+ }
315
+
305
316
  // Verify backward compatibility contract: parseToolResult returns {success: true, data: ...}
306
317
  expect(createdTransaction).toHaveProperty('success');
307
318
  expect(createdTransaction.success).toBe(true);
@@ -319,6 +330,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
319
330
  budget_id: testBudgetId,
320
331
  transaction_id: testTransactionId,
321
332
  });
333
+ if (skipIfRateLimitedResult(getResult)) return;
322
334
 
323
335
  // Validate get_transaction output schema
324
336
  const getValidation = validateOutputSchema(server, 'get_transaction', getResult);
@@ -341,6 +353,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
341
353
  transaction_id: testTransactionId,
342
354
  memo: updatedMemo,
343
355
  });
356
+ if (skipIfRateLimitedResult(updateResult)) return;
344
357
 
345
358
  // Validate update_transaction output schema
346
359
  const updateValidation = validateOutputSchema(server, 'update_transaction', updateResult);
@@ -360,6 +373,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
360
373
  budget_id: testBudgetId,
361
374
  account_id: testAccountId,
362
375
  });
376
+ if (skipIfRateLimitedResult(listResult)) return;
363
377
 
364
378
  // Validate list_transactions output schema
365
379
  const listValidation = validateOutputSchema(server, 'list_transactions', listResult);
@@ -385,6 +399,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
385
399
  budget_id: testBudgetId,
386
400
  transaction_id: testTransactionId,
387
401
  });
402
+ if (skipIfRateLimitedResult(deleteResult)) return;
388
403
 
389
404
  // Validate delete_transaction output schema
390
405
  const deleteValidation = validateOutputSchema(server, 'delete_transaction', deleteResult);
@@ -418,6 +433,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
418
433
  budget_id: testBudgetId,
419
434
  since_date: lastMonth,
420
435
  });
436
+ if (skipIfRateLimitedResult(recentResult)) return;
421
437
  const recentTransactions = parseToolResult(recentResult);
422
438
 
423
439
  expect(recentTransactions.data).toBeDefined();
@@ -429,6 +445,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
429
445
  budget_id: testBudgetId,
430
446
  account_id: testAccountId,
431
447
  });
448
+ if (skipIfRateLimitedResult(accountResult)) return;
432
449
  const accountTransactions = parseToolResult(accountResult);
433
450
 
434
451
  expect(accountTransactions.data).toBeDefined();
@@ -449,6 +466,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
449
466
  budget_id: testBudgetId,
450
467
  account_id: testAccountId,
451
468
  });
469
+ if (skipIfRateLimitedResult(exportResult)) return;
452
470
 
453
471
  // Validate export_transactions output schema
454
472
  const exportValidation = validateOutputSchema(server, 'export_transactions', exportResult);
@@ -469,6 +487,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
469
487
  start_date: '2025-01-01',
470
488
  end_date: '2025-01-31',
471
489
  });
490
+ if (skipIfRateLimitedResult(compareResult)) return;
472
491
 
473
492
  // Validate compare_transactions output schema
474
493
  const compareValidation = validateOutputSchema(server, 'compare_transactions', compareResult);
@@ -508,6 +527,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
508
527
  budget_id: testBudgetId,
509
528
  transactions,
510
529
  });
530
+ if (skipIfRateLimitedResult(createBulkResult)) return;
511
531
 
512
532
  // Validate create_transactions (bulk) output schema
513
533
  const createBulkValidation = validateOutputSchema(
@@ -537,6 +557,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
537
557
  memo: `Updated bulk memo ${index + 1}`,
538
558
  })),
539
559
  });
560
+ if (skipIfRateLimitedResult(updateBulkResult)) return;
540
561
 
541
562
  // Validate update_transactions (bulk) output schema
542
563
  const updateBulkValidation = validateOutputSchema(
@@ -1517,6 +1538,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1517
1538
  budget_id: testBudgetId,
1518
1539
  transactions,
1519
1540
  });
1541
+ if (skipIfRateLimitedResult(result)) return;
1520
1542
  const validation = validateOutputSchema(server, 'create_transactions', result);
1521
1543
  expect(validation.hasSchema).toBe(true);
1522
1544
  expect(validation.valid).toBe(true);
@@ -1546,7 +1568,15 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1546
1568
  memo: 'Before update',
1547
1569
  cleared: 'uncleared',
1548
1570
  });
1571
+ if (skipIfRateLimitedResult(createResult)) return;
1549
1572
  const created = parseToolResult(createResult);
1573
+ if (!created?.data?.transaction?.id) {
1574
+ console.warn(
1575
+ '[rate-limit] Skipping update_transactions schema check because create_transaction returned no transaction data',
1576
+ );
1577
+ return;
1578
+ }
1579
+
1550
1580
  const transactionId = created.data.transaction.id;
1551
1581
  cleanup.trackTransaction(transactionId);
1552
1582
 
@@ -1560,6 +1590,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1560
1590
  },
1561
1591
  ],
1562
1592
  });
1593
+ if (skipIfRateLimitedResult(result)) return;
1563
1594
  const validation = validateOutputSchema(server, 'update_transactions', result);
1564
1595
  expect(validation.hasSchema).toBe(true);
1565
1596
  expect(validation.valid).toBe(true);
@@ -1581,6 +1612,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1581
1612
  start_date: '2025-01-01',
1582
1613
  end_date: '2025-01-31',
1583
1614
  });
1615
+ if (skipIfRateLimitedResult(result)) return;
1584
1616
  const validation = validateOutputSchema(server, 'compare_transactions', result);
1585
1617
  expect(validation.hasSchema).toBe(true);
1586
1618
  expect(validation.valid).toBe(true);
@@ -1611,6 +1643,7 @@ describeE2E('YNAB MCP Server - End-to-End Workflows', () => {
1611
1643
  budget_id: testBudgetId,
1612
1644
  account_id: testAccountId,
1613
1645
  });
1646
+ if (skipIfRateLimitedResult(result)) return;
1614
1647
  const validation = validateOutputSchema(server, 'export_transactions', result);
1615
1648
  expect(validation.hasSchema).toBe(true);
1616
1649
  expect(validation.valid).toBe(true);
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import 'dotenv/config';
5
5
 
6
6
  import { YNABMCPServer } from './server/YNABMCPServer.js';
7
- import { AuthenticationError, ConfigurationError } from './types/index.js';
7
+ import { AuthenticationError, ConfigurationError, ValidationError } from './utils/errors.js';
8
8
 
9
9
  /**
10
10
  * Global server instance for graceful shutdown
@@ -36,14 +36,18 @@ async function gracefulShutdown(signal: string): Promise<void> {
36
36
  * Enhanced error reporting with specific error types
37
37
  */
38
38
  function reportError(error: unknown): void {
39
- if (error instanceof ConfigurationError) {
40
- console.error('❌ Configuration Error:', error.message);
41
- console.error('Please check your environment variables and try again.');
39
+ if (error instanceof ValidationError) {
40
+ console.error('❌ Validation Error:', error.message);
41
+ console.error('Please check your inputs and try again.');
42
42
  process.exit(1);
43
43
  } else if (error instanceof AuthenticationError) {
44
44
  console.error('❌ Authentication Error:', error.message);
45
45
  console.error('Please verify your YNAB access token and try again.');
46
46
  process.exit(1);
47
+ } else if (error instanceof ConfigurationError) {
48
+ console.error('❌ Configuration Error:', error.message);
49
+ console.error('Please check your environment variables and try again.');
50
+ process.exit(1);
47
51
  } else if (error instanceof Error) {
48
52
  console.error('❌ Server Error:', error.message);
49
53
  if (process.env['NODE_ENV'] === 'development') {
@@ -56,30 +60,6 @@ function reportError(error: unknown): void {
56
60
  }
57
61
  }
58
62
 
59
- /**
60
- * Server startup validation
61
- */
62
- function validateStartupEnvironment(): void {
63
- // Check Node.js version
64
- const nodeVersion = process.version;
65
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0] || '0');
66
-
67
- if (majorVersion < 18) {
68
- console.error('❌ Node.js version 18 or higher is required');
69
- console.error(`Current version: ${nodeVersion}`);
70
- process.exit(1);
71
- }
72
-
73
- // Validate environment
74
- if (!process.env['YNAB_ACCESS_TOKEN']) {
75
- console.error('❌ YNAB_ACCESS_TOKEN environment variable is required');
76
- console.error('Please set your YNAB Personal Access Token and try again.');
77
- process.exit(1);
78
- }
79
-
80
- console.error('✅ Environment validation passed');
81
- }
82
-
83
63
  /**
84
64
  * Main entry point for the YNAB MCP Server
85
65
  */
@@ -87,9 +67,6 @@ async function main(): Promise<void> {
87
67
  try {
88
68
  console.error('🚀 Starting YNAB MCP Server...');
89
69
 
90
- // Validate startup environment
91
- validateStartupEnvironment();
92
-
93
70
  // Create and start server
94
71
  serverInstance = new YNABMCPServer();
95
72
  console.error('✅ Server instance created successfully');