@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
@@ -16,12 +16,11 @@ import * as ynab from 'ynab';
16
16
  import {
17
17
  AuthenticationError,
18
18
  ConfigurationError,
19
- ServerConfig,
20
- ErrorHandler,
21
- YNABErrorCode,
22
- ValidationError,
23
- } from '../types/index.js';
24
- import { createErrorHandler } from './errorHandler.js';
19
+ ValidationError as ConfigValidationError,
20
+ } from '../utils/errors.js';
21
+ import { YNABErrorCode, ValidationError } from '../types/index.js';
22
+ import { loadConfig, type AppConfig } from './config.js';
23
+ import { createErrorHandler, ErrorHandler } from './errorHandler.js';
25
24
  import { BudgetResolver } from './budgetResolver.js';
26
25
  import { SecurityMiddleware, withSecurityWrapper } from './securityMiddleware.js';
27
26
  import { handleListBudgets, handleGetBudget, GetBudgetSchema } from '../tools/budgetTools.js';
@@ -87,7 +86,6 @@ import {
87
86
  type DefaultArgumentResolver,
88
87
  type ToolExecutionPayload,
89
88
  } from './toolRegistry.js';
90
- import { validateEnvironment } from './config.js';
91
89
  import { ResourceManager } from './resources.js';
92
90
  import { PromptManager } from './prompts.js';
93
91
  import { DiagnosticManager } from './diagnostics.js';
@@ -107,25 +105,15 @@ import {
107
105
  ListBudgetsOutputSchema,
108
106
  ListAccountsOutputSchema,
109
107
  GetAccountOutputSchema,
110
- CreateAccountOutputSchema,
111
- ListTransactionsOutputSchema,
112
108
  GetTransactionOutputSchema,
113
109
  ExportTransactionsOutputSchema,
114
110
  CompareTransactionsOutputSchema,
115
- CreateTransactionOutputSchema,
116
- CreateTransactionsOutputSchema,
117
- UpdateTransactionOutputSchema,
118
- UpdateTransactionsOutputSchema,
119
- DeleteTransactionOutputSchema,
120
- CreateReceiptSplitTransactionOutputSchema,
121
111
  ListCategoriesOutputSchema,
122
112
  GetCategoryOutputSchema,
123
- UpdateCategoryOutputSchema,
124
113
  ListPayeesOutputSchema,
125
114
  GetPayeeOutputSchema,
126
115
  GetMonthOutputSchema,
127
116
  ListMonthsOutputSchema,
128
- ReconcileAccountOutputSchema,
129
117
  } from '../tools/schemas/outputs/index.js';
130
118
 
131
119
  /**
@@ -134,9 +122,9 @@ import {
134
122
  export class YNABMCPServer {
135
123
  private server: Server;
136
124
  private ynabAPI: ynab.API;
137
- private config: ServerConfig;
138
125
  private exitOnError: boolean;
139
126
  private defaultBudgetId: string | undefined;
127
+ private configInstance: AppConfig;
140
128
  private serverVersion: string;
141
129
  private toolRegistry: ToolRegistry;
142
130
  private resourceManager: ResourceManager;
@@ -149,14 +137,12 @@ export class YNABMCPServer {
149
137
 
150
138
  constructor(exitOnError: boolean = true) {
151
139
  this.exitOnError = exitOnError;
152
- // Validate environment variables
153
- this.config = validateEnvironment();
154
- if (this.config.defaultBudgetId !== undefined) {
155
- this.defaultBudgetId = this.config.defaultBudgetId;
156
- }
140
+ this.configInstance = loadConfig();
141
+ // Config is now imported and validated at startup
142
+ this.defaultBudgetId = process.env['YNAB_DEFAULT_BUDGET_ID'];
157
143
 
158
144
  // Initialize YNAB API
159
- this.ynabAPI = new ynab.API(this.config.accessToken);
145
+ this.ynabAPI = new ynab.API(this.configInstance.YNAB_ACCESS_TOKEN);
160
146
 
161
147
  // Determine server version (prefer package.json)
162
148
  this.serverVersion = this.readPackageVersion() ?? '0.0.0';
@@ -169,9 +155,9 @@ export class YNABMCPServer {
169
155
  },
170
156
  {
171
157
  capabilities: {
172
- tools: {},
173
- resources: {},
174
- prompts: {},
158
+ tools: { listChanged: true },
159
+ resources: { listChanged: true },
160
+ prompts: { listChanged: true },
175
161
  },
176
162
  },
177
163
  );
@@ -217,7 +203,7 @@ export class YNABMCPServer {
217
203
  },
218
204
  },
219
205
  validateAccessToken: (token: string) => {
220
- const expected = this.config.accessToken.trim();
206
+ const expected = this.configInstance.YNAB_ACCESS_TOKEN.trim();
221
207
  const provided = typeof token === 'string' ? token.trim() : '';
222
208
  if (!provided) {
223
209
  throw this.errorHandler.createYNABError(
@@ -269,6 +255,10 @@ export class YNABMCPServer {
269
255
  await this.ynabAPI.user.getUser();
270
256
  return true;
271
257
  } catch (error) {
258
+ if (this.isMalformedTokenResponse(error)) {
259
+ throw new AuthenticationError('Unexpected response from YNAB during token validation');
260
+ }
261
+
272
262
  if (error instanceof Error) {
273
263
  // Check for authentication-related errors
274
264
  if (error.message.includes('401') || error.message.includes('Unauthorized')) {
@@ -277,9 +267,37 @@ export class YNABMCPServer {
277
267
  if (error.message.includes('403') || error.message.includes('Forbidden')) {
278
268
  throw new AuthenticationError('YNAB access token has insufficient permissions');
279
269
  }
270
+
271
+ const reason = error.message || String(error);
272
+ throw new AuthenticationError(`Token validation failed: ${reason}`);
280
273
  }
281
- throw new AuthenticationError(`Token validation failed: ${error}`);
274
+
275
+ throw new AuthenticationError(`Token validation failed: ${String(error)}`);
276
+ }
277
+ }
278
+
279
+ private isMalformedTokenResponse(error: unknown): boolean {
280
+ if (error instanceof SyntaxError) {
281
+ return true;
282
282
  }
283
+
284
+ const message =
285
+ typeof error === 'string'
286
+ ? error
287
+ : typeof (error as { message?: unknown })?.message === 'string'
288
+ ? String((error as { message: unknown }).message)
289
+ : null;
290
+
291
+ if (!message) {
292
+ return false;
293
+ }
294
+
295
+ const normalized = message.toLowerCase();
296
+ return (
297
+ normalized.includes('unexpected token') ||
298
+ normalized.includes('unexpected end of json') ||
299
+ normalized.includes('<html')
300
+ );
283
301
  }
284
302
 
285
303
  /**
@@ -345,7 +363,7 @@ export class YNABMCPServer {
345
363
  minifyOverride?: boolean;
346
364
  } = {
347
365
  name: request.params.name,
348
- accessToken: this.config.accessToken,
366
+ accessToken: this.configInstance.YNAB_ACCESS_TOKEN,
349
367
  arguments: sanitizedArgs ?? {},
350
368
  };
351
369
 
@@ -436,6 +454,8 @@ export class YNABMCPServer {
436
454
  pretty_spaces: z.number().int().min(0).max(10).optional(),
437
455
  })
438
456
  .strict();
457
+ // Permissive object schema used where hosts require a top-level object type
458
+ const LooseObjectSchema = z.object({}).passthrough();
439
459
 
440
460
  register({
441
461
  name: 'list_budgets',
@@ -576,7 +596,7 @@ export class YNABMCPServer {
576
596
  name: 'create_account',
577
597
  description: 'Create a new account in the specified budget',
578
598
  inputSchema: CreateAccountSchema,
579
- outputSchema: CreateAccountOutputSchema,
599
+ outputSchema: LooseObjectSchema,
580
600
  handler: adaptWrite(handleCreateAccount),
581
601
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateAccountSchema>>(),
582
602
  metadata: {
@@ -591,7 +611,7 @@ export class YNABMCPServer {
591
611
  name: 'list_transactions',
592
612
  description: 'List transactions for a budget with optional filtering',
593
613
  inputSchema: ListTransactionsSchema,
594
- outputSchema: ListTransactionsOutputSchema,
614
+ outputSchema: LooseObjectSchema,
595
615
  handler: adaptWithDelta(handleListTransactions),
596
616
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListTransactionsSchema>>(),
597
617
  metadata: {
@@ -638,7 +658,7 @@ export class YNABMCPServer {
638
658
  description:
639
659
  'Guided reconciliation workflow with human narrative, insight detection, and optional execution (create/update/unclear). Set include_structured_data=true to also get full JSON output (large).',
640
660
  inputSchema: ReconcileAccountSchema,
641
- outputSchema: ReconcileAccountOutputSchema,
661
+ outputSchema: LooseObjectSchema,
642
662
  handler: adaptWithDelta(handleReconcileAccount),
643
663
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof ReconcileAccountSchema>>(),
644
664
  metadata: {
@@ -668,7 +688,7 @@ export class YNABMCPServer {
668
688
  name: 'create_transaction',
669
689
  description: 'Create a new transaction in the specified budget and account',
670
690
  inputSchema: CreateTransactionSchema,
671
- outputSchema: CreateTransactionOutputSchema,
691
+ outputSchema: LooseObjectSchema,
672
692
  handler: adaptWrite(handleCreateTransaction),
673
693
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionSchema>>(),
674
694
  metadata: {
@@ -684,7 +704,7 @@ export class YNABMCPServer {
684
704
  description:
685
705
  'Create multiple transactions in a single batch (1-100 items) with duplicate detection, dry-run validation, and automatic response size management with correlation metadata.',
686
706
  inputSchema: CreateTransactionsSchema,
687
- outputSchema: CreateTransactionsOutputSchema,
707
+ outputSchema: LooseObjectSchema,
688
708
  handler: adaptWrite(handleCreateTransactions),
689
709
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionsSchema>>(),
690
710
  metadata: {
@@ -700,7 +720,7 @@ export class YNABMCPServer {
700
720
  description:
701
721
  'Update multiple transactions in a single batch (1-100 items) with dry-run validation, automatic cache invalidation, and response size management. Supports optional original_account_id and original_date metadata for efficient cache invalidation.',
702
722
  inputSchema: UpdateTransactionsSchema,
703
- outputSchema: UpdateTransactionsOutputSchema,
723
+ outputSchema: LooseObjectSchema,
704
724
  handler: adaptWrite(handleUpdateTransactions),
705
725
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionsSchema>>(),
706
726
  metadata: {
@@ -715,7 +735,7 @@ export class YNABMCPServer {
715
735
  name: 'create_receipt_split_transaction',
716
736
  description: 'Create a split transaction from receipt items with proportional tax allocation',
717
737
  inputSchema: CreateReceiptSplitTransactionSchema,
718
- outputSchema: CreateReceiptSplitTransactionOutputSchema,
738
+ outputSchema: LooseObjectSchema,
719
739
  handler: adaptWrite(handleCreateReceiptSplitTransaction),
720
740
  defaultArgumentResolver:
721
741
  resolveBudgetId<z.infer<typeof CreateReceiptSplitTransactionSchema>>(),
@@ -731,7 +751,7 @@ export class YNABMCPServer {
731
751
  name: 'update_transaction',
732
752
  description: 'Update an existing transaction',
733
753
  inputSchema: UpdateTransactionSchema,
734
- outputSchema: UpdateTransactionOutputSchema,
754
+ outputSchema: LooseObjectSchema,
735
755
  handler: adaptWrite(handleUpdateTransaction),
736
756
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionSchema>>(),
737
757
  metadata: {
@@ -746,7 +766,7 @@ export class YNABMCPServer {
746
766
  name: 'delete_transaction',
747
767
  description: 'Delete a transaction from the specified budget',
748
768
  inputSchema: DeleteTransactionSchema,
749
- outputSchema: DeleteTransactionOutputSchema,
769
+ outputSchema: LooseObjectSchema,
750
770
  handler: adaptWrite(handleDeleteTransaction),
751
771
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof DeleteTransactionSchema>>(),
752
772
  metadata: {
@@ -791,7 +811,7 @@ export class YNABMCPServer {
791
811
  name: 'update_category',
792
812
  description: 'Update the budgeted amount for a category in the current month',
793
813
  inputSchema: UpdateCategorySchema,
794
- outputSchema: UpdateCategoryOutputSchema,
814
+ outputSchema: LooseObjectSchema,
795
815
  handler: adaptWrite(handleUpdateCategory),
796
816
  defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateCategorySchema>>(),
797
817
  metadata: {
@@ -999,8 +1019,14 @@ export class YNABMCPServer {
999
1019
 
1000
1020
  console.error('YNAB MCP Server started successfully');
1001
1021
  } catch (error) {
1002
- if (error instanceof AuthenticationError || error instanceof ConfigurationError) {
1003
- console.error(`Server startup failed: ${error.message}`);
1022
+ if (
1023
+ error instanceof AuthenticationError ||
1024
+ error instanceof ConfigurationError ||
1025
+ error instanceof ConfigValidationError ||
1026
+ error instanceof ValidationError ||
1027
+ (error as { name?: string })?.name === 'ValidationError'
1028
+ ) {
1029
+ console.error(`Server startup failed: ${error instanceof Error ? error.message : error}`);
1004
1030
  if (this.exitOnError) {
1005
1031
  process.exit(1);
1006
1032
  } else {
@@ -1172,6 +1198,19 @@ export class YNABMCPServer {
1172
1198
  } catch {
1173
1199
  // ignore
1174
1200
  }
1201
+ try {
1202
+ // CJS bundles can rely on __dirname being defined; add nearby package.json fallbacks
1203
+ const dir = typeof __dirname === 'string' ? __dirname : undefined;
1204
+ if (dir) {
1205
+ candidates.push(
1206
+ path.resolve(dir, '../../package.json'),
1207
+ path.resolve(dir, '../package.json'),
1208
+ path.resolve(dir, 'package.json'),
1209
+ );
1210
+ }
1211
+ } catch {
1212
+ // ignore additional fallbacks
1213
+ }
1175
1214
  for (const p of candidates) {
1176
1215
  try {
1177
1216
  if (fs.existsSync(p)) {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import { YNABMCPServer } from '../YNABMCPServer.js';
3
- import { AuthenticationError, ConfigurationError } from '../../types/index.js';
3
+ import { ValidationError } from '../../types/index.js';
4
4
  import { ToolRegistry } from '../toolRegistry.js';
5
5
  import { cacheManager } from '../../server/cacheManager.js';
6
6
  import { responseFormatter } from '../../server/responseFormatter.js';
@@ -42,16 +42,13 @@ describeIntegration('YNABMCPServer', () => {
42
42
  );
43
43
 
44
44
  it(
45
- 'should throw ConfigurationError when YNAB_ACCESS_TOKEN is missing',
45
+ 'should throw ValidationError when YNAB_ACCESS_TOKEN is missing',
46
46
  { meta: { tier: 'domain', domain: 'server' } },
47
47
  () => {
48
48
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
49
49
  delete process.env['YNAB_ACCESS_TOKEN'];
50
50
 
51
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
52
- expect(() => new YNABMCPServer()).toThrow(
53
- 'YNAB_ACCESS_TOKEN environment variable is required but not set',
54
- );
51
+ expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
55
52
 
56
53
  // Restore token
57
54
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;
@@ -59,13 +56,12 @@ describeIntegration('YNABMCPServer', () => {
59
56
  );
60
57
 
61
58
  it(
62
- 'should throw ConfigurationError when YNAB_ACCESS_TOKEN is empty string',
59
+ 'should throw ValidationError when YNAB_ACCESS_TOKEN is empty string',
63
60
  { meta: { tier: 'domain', domain: 'server' } },
64
61
  () => {
65
62
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
66
63
  process.env['YNAB_ACCESS_TOKEN'] = '';
67
64
 
68
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
69
65
  expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
70
66
 
71
67
  // Restore token
@@ -74,13 +70,12 @@ describeIntegration('YNABMCPServer', () => {
74
70
  );
75
71
 
76
72
  it(
77
- 'should throw ConfigurationError when YNAB_ACCESS_TOKEN is only whitespace',
73
+ 'should throw ValidationError when YNAB_ACCESS_TOKEN is only whitespace',
78
74
  { meta: { tier: 'domain', domain: 'server' } },
79
75
  () => {
80
76
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
81
77
  process.env['YNAB_ACCESS_TOKEN'] = ' ';
82
78
 
83
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
84
79
  expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
85
80
 
86
81
  // Restore token
@@ -167,7 +162,10 @@ describeIntegration('YNABMCPServer', () => {
167
162
 
168
163
  try {
169
164
  const invalidServer = new YNABMCPServer(false);
170
- await expect(invalidServer.validateToken()).rejects.toThrow(AuthenticationError);
165
+ await expect(invalidServer.validateToken()).rejects.toHaveProperty(
166
+ 'name',
167
+ 'AuthenticationError',
168
+ );
171
169
  } finally {
172
170
  // Restore original token
173
171
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;
@@ -200,7 +198,7 @@ describeIntegration('YNABMCPServer', () => {
200
198
  } catch (error) {
201
199
  // Expected to fail on stdio connection in test environment
202
200
  // Token was already validated above, so this error should be transport-related
203
- expect(error).not.toBeInstanceOf(ConfigurationError);
201
+ expect(error).not.toBeInstanceOf(ValidationError);
204
202
  }
205
203
 
206
204
  consoleSpy.mockRestore();
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from 'vite
2
2
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
3
 
4
4
  import { YNABMCPServer } from '../YNABMCPServer.js';
5
- import { AuthenticationError, ConfigurationError, ValidationError } from '../../types/index.js';
5
+ import { AuthenticationError, ValidationError } from '../../types/index.js';
6
6
  import { ToolRegistry } from '../toolRegistry.js';
7
7
  import { cacheManager } from '../../server/cacheManager.js';
8
8
  import { responseFormatter } from '../../server/responseFormatter.js';
@@ -80,41 +80,56 @@ describe('YNABMCPServer', () => {
80
80
  expect(server.getYNABAPI()).toBeDefined();
81
81
  });
82
82
 
83
- it('should throw ConfigurationError when YNAB_ACCESS_TOKEN is missing', () => {
83
+ it('should throw ValidationError when YNAB_ACCESS_TOKEN is missing', () => {
84
84
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
85
85
  delete process.env['YNAB_ACCESS_TOKEN'];
86
86
 
87
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
88
- expect(() => new YNABMCPServer()).toThrow(
89
- 'YNAB_ACCESS_TOKEN environment variable is required but not set',
90
- );
87
+ expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
91
88
 
92
89
  // Restore token
93
90
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;
94
91
  });
95
92
 
96
- it('should throw ConfigurationError when YNAB_ACCESS_TOKEN is empty string', () => {
93
+ it('should throw ValidationError when YNAB_ACCESS_TOKEN is empty string', () => {
97
94
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
98
95
  process.env['YNAB_ACCESS_TOKEN'] = '';
99
96
 
100
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
101
97
  expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
102
98
 
103
99
  // Restore token
104
100
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;
105
101
  });
106
102
 
107
- it('should throw ConfigurationError when YNAB_ACCESS_TOKEN is only whitespace', () => {
103
+ it('should throw ValidationError when YNAB_ACCESS_TOKEN is only whitespace', () => {
108
104
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
109
105
  process.env['YNAB_ACCESS_TOKEN'] = ' ';
110
106
 
111
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
112
107
  expect(() => new YNABMCPServer()).toThrow('YNAB_ACCESS_TOKEN must be a non-empty string');
113
108
 
114
109
  // Restore token
115
110
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;
116
111
  });
117
112
 
113
+ it('should reload configuration for each server instance', () => {
114
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
115
+
116
+ process.env['YNAB_ACCESS_TOKEN'] = 'token-one';
117
+ const firstServer = new YNABMCPServer(false);
118
+ const firstConfig = (
119
+ firstServer as unknown as { configInstance: { YNAB_ACCESS_TOKEN: string } }
120
+ ).configInstance;
121
+ expect(firstConfig.YNAB_ACCESS_TOKEN).toBe('token-one');
122
+
123
+ process.env['YNAB_ACCESS_TOKEN'] = 'token-two';
124
+ const secondServer = new YNABMCPServer(false);
125
+ const secondConfig = (
126
+ secondServer as unknown as { configInstance: { YNAB_ACCESS_TOKEN: string } }
127
+ ).configInstance;
128
+ expect(secondConfig.YNAB_ACCESS_TOKEN).toBe('token-two');
129
+
130
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
131
+ });
132
+
118
133
  it('should trim whitespace from access token', () => {
119
134
  const originalToken = process.env['YNAB_ACCESS_TOKEN'];
120
135
  process.env['YNAB_ACCESS_TOKEN'] = ` ${originalToken} `;
@@ -193,7 +208,7 @@ describe('YNABMCPServer', () => {
193
208
  // Expected to fail on stdio connection in test environment
194
209
  // But should not fail on token validation
195
210
  expect(error).not.toBeInstanceOf(AuthenticationError);
196
- expect(error).not.toBeInstanceOf(ConfigurationError);
211
+ expect(error).not.toBeInstanceOf(ValidationError);
197
212
  }
198
213
 
199
214
  consoleSpy.mockRestore();
@@ -739,10 +754,7 @@ describe('YNABMCPServer', () => {
739
754
  delete process.env['YNAB_ACCESS_TOKEN'];
740
755
 
741
756
  try {
742
- expect(() => new YNABMCPServer()).toThrow(ConfigurationError);
743
- expect(() => new YNABMCPServer()).toThrow(
744
- 'YNAB_ACCESS_TOKEN environment variable is required but not set',
745
- );
757
+ expect(() => new YNABMCPServer()).toThrow(/YNAB_ACCESS_TOKEN/i);
746
758
  } finally {
747
759
  // Restore token
748
760
  process.env['YNAB_ACCESS_TOKEN'] = originalToken;