@dizzlkheinz/ynab-mcpb 0.12.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 (435) hide show
  1. package/.chunkhound.json +11 -0
  2. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
  3. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
  4. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
  5. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
  6. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
  7. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
  8. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
  9. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
  10. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
  11. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
  12. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
  13. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
  14. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
  15. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
  16. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
  17. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
  18. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
  19. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
  20. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
  21. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
  22. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
  23. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
  24. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
  25. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
  26. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
  27. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
  28. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
  29. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
  30. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
  31. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
  32. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
  33. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
  34. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
  35. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
  36. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
  37. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
  38. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
  39. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
  40. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
  41. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
  42. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
  43. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
  44. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
  45. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
  46. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
  47. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
  48. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
  49. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
  50. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
  51. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
  52. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
  53. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
  54. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
  55. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
  56. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
  57. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
  58. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
  59. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
  60. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
  61. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
  62. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
  63. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
  64. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
  65. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
  66. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
  67. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
  68. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
  69. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
  70. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
  71. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
  72. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
  73. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
  74. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
  75. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
  76. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
  77. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
  78. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
  79. package/.dxtignore +57 -0
  80. package/.env.example +44 -0
  81. package/.gemini/settings.json +8 -0
  82. package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  83. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  84. package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  85. package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
  86. package/.github/pull_request_template.md +41 -0
  87. package/.github/workflows/ci-tests.yml +41 -0
  88. package/.github/workflows/claude-code-review.yml +57 -0
  89. package/.github/workflows/claude.yml +50 -0
  90. package/.github/workflows/full-integration.yml +22 -0
  91. package/.github/workflows/pr-description-check.yml +88 -0
  92. package/.github/workflows/publish.yml +33 -0
  93. package/.github/workflows/release.yml +89 -0
  94. package/.mcpbignore +58 -0
  95. package/.prettierignore +10 -0
  96. package/.prettierrc.json +10 -0
  97. package/ADOS-2-Module-1-Complete-Manual.md +757 -0
  98. package/AGENTS.md +36 -0
  99. package/CHANGELOG.md +187 -0
  100. package/CLAUDE.md +414 -0
  101. package/CODEREVIEW_RESPONSE.md +128 -0
  102. package/LICENSE +17 -0
  103. package/NUL +1 -0
  104. package/README.md +222 -0
  105. package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
  106. package/TESTING_NOTES.md +217 -0
  107. package/WARP.md +245 -0
  108. package/accountactivity-merged.csv +149 -0
  109. package/bin/ynab-mcp-server.cjs +4 -0
  110. package/bin/ynab-mcp-server.js +8 -0
  111. package/bundle-analysis.html +13110 -0
  112. package/dist/bundle/index.cjs +124 -0
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.js +85 -0
  115. package/dist/server/YNABMCPServer.d.ts +264 -0
  116. package/dist/server/YNABMCPServer.js +845 -0
  117. package/dist/server/budgetResolver.d.ts +15 -0
  118. package/dist/server/budgetResolver.js +99 -0
  119. package/dist/server/cacheManager.d.ts +74 -0
  120. package/dist/server/cacheManager.js +306 -0
  121. package/dist/server/config.d.ts +3 -0
  122. package/dist/server/config.js +19 -0
  123. package/dist/server/deltaCache.d.ts +61 -0
  124. package/dist/server/deltaCache.js +206 -0
  125. package/dist/server/deltaCache.merge.d.ts +9 -0
  126. package/dist/server/deltaCache.merge.js +111 -0
  127. package/dist/server/diagnostics.d.ts +90 -0
  128. package/dist/server/diagnostics.js +163 -0
  129. package/dist/server/errorHandler.d.ts +69 -0
  130. package/dist/server/errorHandler.js +524 -0
  131. package/dist/server/prompts.d.ts +31 -0
  132. package/dist/server/prompts.js +205 -0
  133. package/dist/server/rateLimiter.d.ts +27 -0
  134. package/dist/server/rateLimiter.js +82 -0
  135. package/dist/server/requestLogger.d.ts +62 -0
  136. package/dist/server/requestLogger.js +190 -0
  137. package/dist/server/resources.d.ts +39 -0
  138. package/dist/server/resources.js +85 -0
  139. package/dist/server/responseFormatter.d.ts +14 -0
  140. package/dist/server/responseFormatter.js +42 -0
  141. package/dist/server/securityMiddleware.d.ts +87 -0
  142. package/dist/server/securityMiddleware.js +117 -0
  143. package/dist/server/serverKnowledgeStore.d.ts +11 -0
  144. package/dist/server/serverKnowledgeStore.js +42 -0
  145. package/dist/server/toolRegistry.d.ts +85 -0
  146. package/dist/server/toolRegistry.js +272 -0
  147. package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
  148. package/dist/tools/__tests__/deltaTestUtils.js +26 -0
  149. package/dist/tools/accountTools.d.ts +37 -0
  150. package/dist/tools/accountTools.js +175 -0
  151. package/dist/tools/budgetTools.d.ts +10 -0
  152. package/dist/tools/budgetTools.js +68 -0
  153. package/dist/tools/categoryTools.d.ts +27 -0
  154. package/dist/tools/categoryTools.js +232 -0
  155. package/dist/tools/compareTransactions/formatter.d.ts +71 -0
  156. package/dist/tools/compareTransactions/formatter.js +97 -0
  157. package/dist/tools/compareTransactions/index.d.ts +30 -0
  158. package/dist/tools/compareTransactions/index.js +160 -0
  159. package/dist/tools/compareTransactions/matcher.d.ts +12 -0
  160. package/dist/tools/compareTransactions/matcher.js +140 -0
  161. package/dist/tools/compareTransactions/parser.d.ts +14 -0
  162. package/dist/tools/compareTransactions/parser.js +430 -0
  163. package/dist/tools/compareTransactions/types.d.ts +27 -0
  164. package/dist/tools/compareTransactions/types.js +1 -0
  165. package/dist/tools/compareTransactions.d.ts +1 -0
  166. package/dist/tools/compareTransactions.js +1 -0
  167. package/dist/tools/deltaFetcher.d.ts +22 -0
  168. package/dist/tools/deltaFetcher.js +137 -0
  169. package/dist/tools/deltaSupport.d.ts +20 -0
  170. package/dist/tools/deltaSupport.js +176 -0
  171. package/dist/tools/exportTransactions.d.ts +17 -0
  172. package/dist/tools/exportTransactions.js +191 -0
  173. package/dist/tools/monthTools.d.ts +16 -0
  174. package/dist/tools/monthTools.js +107 -0
  175. package/dist/tools/payeeTools.d.ts +17 -0
  176. package/dist/tools/payeeTools.js +82 -0
  177. package/dist/tools/reconcileAdapter.d.ts +25 -0
  178. package/dist/tools/reconcileAdapter.js +167 -0
  179. package/dist/tools/reconciliation/analyzer.d.ts +3 -0
  180. package/dist/tools/reconciliation/analyzer.js +567 -0
  181. package/dist/tools/reconciliation/executor.d.ts +94 -0
  182. package/dist/tools/reconciliation/executor.js +611 -0
  183. package/dist/tools/reconciliation/index.d.ts +54 -0
  184. package/dist/tools/reconciliation/index.js +249 -0
  185. package/dist/tools/reconciliation/matcher.d.ts +3 -0
  186. package/dist/tools/reconciliation/matcher.js +160 -0
  187. package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
  188. package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
  189. package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
  190. package/dist/tools/reconciliation/recommendationEngine.js +273 -0
  191. package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
  192. package/dist/tools/reconciliation/reportFormatter.js +214 -0
  193. package/dist/tools/reconciliation/types.d.ts +172 -0
  194. package/dist/tools/reconciliation/types.js +7 -0
  195. package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
  196. package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
  197. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
  198. package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
  199. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
  200. package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
  201. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
  202. package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
  203. package/dist/tools/schemas/outputs/index.d.ts +14 -0
  204. package/dist/tools/schemas/outputs/index.js +14 -0
  205. package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
  206. package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
  207. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
  208. package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
  209. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
  210. package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
  211. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
  212. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
  213. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
  214. package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
  215. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
  216. package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
  217. package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
  218. package/dist/tools/schemas/shared/commonOutputs.js +27 -0
  219. package/dist/tools/toolCategories.d.ts +32 -0
  220. package/dist/tools/toolCategories.js +32 -0
  221. package/dist/tools/transactionTools.d.ts +315 -0
  222. package/dist/tools/transactionTools.js +1722 -0
  223. package/dist/tools/utilityTools.d.ts +10 -0
  224. package/dist/tools/utilityTools.js +56 -0
  225. package/dist/types/index.d.ts +20 -0
  226. package/dist/types/index.js +16 -0
  227. package/dist/types/toolAnnotations.d.ts +7 -0
  228. package/dist/types/toolAnnotations.js +1 -0
  229. package/dist/utils/amountUtils.d.ts +3 -0
  230. package/dist/utils/amountUtils.js +10 -0
  231. package/dist/utils/dateUtils.d.ts +9 -0
  232. package/dist/utils/dateUtils.js +43 -0
  233. package/dist/utils/money.d.ts +21 -0
  234. package/dist/utils/money.js +51 -0
  235. package/docs/README.md +72 -0
  236. package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
  237. package/docs/assets/schemas/reconciliation-v2.json +338 -0
  238. package/docs/getting-started/CONFIGURATION.md +175 -0
  239. package/docs/getting-started/INSTALLATION.md +333 -0
  240. package/docs/getting-started/QUICKSTART.md +282 -0
  241. package/docs/guides/ARCHITECTURE.md +650 -0
  242. package/docs/guides/DEPLOYMENT.md +189 -0
  243. package/docs/guides/INTEGRATION_TESTING.md +730 -0
  244. package/docs/guides/TESTING.md +591 -0
  245. package/docs/reconciliation-flow.md +83 -0
  246. package/docs/reference/API.md +1450 -0
  247. package/docs/reference/EXAMPLES.md +946 -0
  248. package/docs/reference/TOOLS.md +348 -0
  249. package/docs/reference/TROUBLESHOOTING.md +481 -0
  250. package/esbuild.config.mjs +68 -0
  251. package/eslint.config.js +49 -0
  252. package/fix-types.sh +17 -0
  253. package/meta.json +12550 -0
  254. package/package.json +105 -0
  255. package/package.json.tmp +105 -0
  256. package/scripts/analyze-bundle.mjs +41 -0
  257. package/scripts/create-pr-description.js +203 -0
  258. package/scripts/generate-mcpb.ps1 +96 -0
  259. package/scripts/run-domain-integration-tests.js +33 -0
  260. package/scripts/run-generate-mcpb.js +29 -0
  261. package/scripts/run-throttled-integration-tests.js +116 -0
  262. package/scripts/test-delta-params.mjs +140 -0
  263. package/scripts/test-recommendations.ts +53 -0
  264. package/scripts/tmpTransaction.ts +48 -0
  265. package/scripts/validate-env.js +122 -0
  266. package/scripts/verify-build.js +105 -0
  267. package/scripts/watch-and-restart.ps1 +50 -0
  268. package/src/__tests__/comprehensive.integration.test.ts +1196 -0
  269. package/src/__tests__/delta.performance.test.ts +80 -0
  270. package/src/__tests__/performance.test.ts +725 -0
  271. package/src/__tests__/setup.ts +449 -0
  272. package/src/__tests__/testRunner.ts +444 -0
  273. package/src/__tests__/testUtils.ts +563 -0
  274. package/src/__tests__/workflows.e2e.test.ts +1675 -0
  275. package/src/index.ts +124 -0
  276. package/src/server/.gitkeep +1 -0
  277. package/src/server/YNABMCPServer.ts +1188 -0
  278. package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
  279. package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
  280. package/src/server/__tests__/budgetResolver.test.ts +425 -0
  281. package/src/server/__tests__/cacheManager.test.ts +880 -0
  282. package/src/server/__tests__/config.test.ts +166 -0
  283. package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
  284. package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
  285. package/src/server/__tests__/deltaCache.test.ts +774 -0
  286. package/src/server/__tests__/diagnostics.test.ts +823 -0
  287. package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
  288. package/src/server/__tests__/errorHandler.test.ts +416 -0
  289. package/src/server/__tests__/prompts.test.ts +354 -0
  290. package/src/server/__tests__/rateLimiter.test.ts +314 -0
  291. package/src/server/__tests__/requestLogger.test.ts +408 -0
  292. package/src/server/__tests__/resources.test.ts +299 -0
  293. package/src/server/__tests__/security.integration.test.ts +426 -0
  294. package/src/server/__tests__/securityMiddleware.test.ts +449 -0
  295. package/src/server/__tests__/server-startup.integration.test.ts +477 -0
  296. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
  297. package/src/server/__tests__/toolRegistry.test.ts +855 -0
  298. package/src/server/budgetResolver.ts +235 -0
  299. package/src/server/cacheManager.ts +503 -0
  300. package/src/server/config.ts +41 -0
  301. package/src/server/deltaCache.merge.ts +149 -0
  302. package/src/server/deltaCache.ts +341 -0
  303. package/src/server/diagnostics.ts +338 -0
  304. package/src/server/errorHandler.ts +756 -0
  305. package/src/server/prompts.ts +291 -0
  306. package/src/server/rateLimiter.ts +156 -0
  307. package/src/server/requestLogger.ts +344 -0
  308. package/src/server/resources.ts +168 -0
  309. package/src/server/responseFormatter.ts +51 -0
  310. package/src/server/securityMiddleware.ts +236 -0
  311. package/src/server/serverKnowledgeStore.ts +91 -0
  312. package/src/server/toolRegistry.ts +489 -0
  313. package/src/tools/.gitkeep +1 -0
  314. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
  315. package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
  316. package/src/tools/__tests__/accountTools.test.ts +653 -0
  317. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
  318. package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
  319. package/src/tools/__tests__/budgetTools.test.ts +423 -0
  320. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
  321. package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
  322. package/src/tools/__tests__/categoryTools.test.ts +622 -0
  323. package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
  324. package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
  325. package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
  326. package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
  327. package/src/tools/__tests__/compareTransactions.test.ts +342 -0
  328. package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
  329. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
  330. package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
  331. package/src/tools/__tests__/deltaSupport.test.ts +188 -0
  332. package/src/tools/__tests__/deltaTestUtils.ts +46 -0
  333. package/src/tools/__tests__/exportTransactions.test.ts +213 -0
  334. package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
  335. package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
  336. package/src/tools/__tests__/monthTools.test.ts +523 -0
  337. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
  338. package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
  339. package/src/tools/__tests__/payeeTools.test.ts +445 -0
  340. package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
  341. package/src/tools/__tests__/transactionTools.test.ts +3521 -0
  342. package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
  343. package/src/tools/__tests__/utilityTools.test.ts +205 -0
  344. package/src/tools/accountTools.ts +283 -0
  345. package/src/tools/budgetTools.ts +112 -0
  346. package/src/tools/categoryTools.ts +366 -0
  347. package/src/tools/compareTransactions/formatter.ts +163 -0
  348. package/src/tools/compareTransactions/index.ts +228 -0
  349. package/src/tools/compareTransactions/matcher.ts +240 -0
  350. package/src/tools/compareTransactions/parser.ts +557 -0
  351. package/src/tools/compareTransactions/types.ts +60 -0
  352. package/src/tools/compareTransactions.ts +3 -0
  353. package/src/tools/deltaFetcher.ts +278 -0
  354. package/src/tools/deltaSupport.ts +293 -0
  355. package/src/tools/exportTransactions.ts +273 -0
  356. package/src/tools/monthTools.ts +164 -0
  357. package/src/tools/payeeTools.ts +140 -0
  358. package/src/tools/reconcileAdapter.ts +312 -0
  359. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
  360. package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
  361. package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
  362. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
  363. package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
  364. package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
  365. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
  366. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
  367. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
  368. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
  369. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
  370. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
  371. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
  372. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
  373. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
  374. package/src/tools/reconciliation/analyzer.ts +824 -0
  375. package/src/tools/reconciliation/executor.ts +880 -0
  376. package/src/tools/reconciliation/index.ts +400 -0
  377. package/src/tools/reconciliation/matcher.ts +269 -0
  378. package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
  379. package/src/tools/reconciliation/recommendationEngine.ts +506 -0
  380. package/src/tools/reconciliation/reportFormatter.ts +363 -0
  381. package/src/tools/reconciliation/types.ts +314 -0
  382. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
  383. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
  384. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
  385. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
  386. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
  387. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
  388. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
  389. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
  390. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
  391. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
  392. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
  393. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
  394. package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
  395. package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
  396. package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
  397. package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
  398. package/src/tools/schemas/outputs/index.ts +270 -0
  399. package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
  400. package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
  401. package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
  402. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
  403. package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
  404. package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
  405. package/src/tools/schemas/shared/commonOutputs.ts +140 -0
  406. package/src/tools/toolCategories.ts +140 -0
  407. package/src/tools/transactionTools.ts +2509 -0
  408. package/src/tools/utilityTools.ts +90 -0
  409. package/src/types/.gitkeep +1 -0
  410. package/src/types/__tests__/index.test.ts +52 -0
  411. package/src/types/index.ts +67 -0
  412. package/src/types/integration-tests.d.ts +35 -0
  413. package/src/types/toolAnnotations.ts +44 -0
  414. package/src/utils/__tests__/dateUtils.test.ts +170 -0
  415. package/src/utils/__tests__/money.test.ts +189 -0
  416. package/src/utils/amountUtils.ts +32 -0
  417. package/src/utils/dateUtils.ts +108 -0
  418. package/src/utils/money.ts +123 -0
  419. package/test-csv-sample.csv +28 -0
  420. package/test-exports/sample_bank_statement.csv +7 -0
  421. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
  422. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
  423. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
  424. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
  425. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
  426. package/test-reconcile-autodetect.js +40 -0
  427. package/test-reconcile-tool.js +152 -0
  428. package/test-reconcile-with-csv.cjs +89 -0
  429. package/test-statement.csv +8 -0
  430. package/test_debug.js +47 -0
  431. package/test_simple.mjs +16 -0
  432. package/tsconfig.json +31 -0
  433. package/tsconfig.prod.json +18 -0
  434. package/vitest-reporters/split-json-reporter.ts +211 -0
  435. package/vitest.config.ts +96 -0
@@ -0,0 +1,523 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import * as ynab from 'ynab';
3
+ import {
4
+ handleGetMonth,
5
+ handleListMonths,
6
+ GetMonthSchema,
7
+ ListMonthsSchema,
8
+ } from '../monthTools.js';
9
+ import { createDeltaFetcherMock, createRejectingDeltaFetcherMock } from './deltaTestUtils.js';
10
+
11
+ // Mock the cache manager
12
+ vi.mock('../../server/cacheManager.js', () => ({
13
+ cacheManager: {
14
+ wrap: vi.fn(),
15
+ has: vi.fn(),
16
+ delete: vi.fn(),
17
+ deleteMany: vi.fn(),
18
+ deleteByPrefix: vi.fn(),
19
+ deleteByBudgetId: vi.fn(),
20
+ clear: vi.fn(),
21
+ },
22
+ CacheManager: {
23
+ generateKey: vi.fn(),
24
+ },
25
+ CACHE_TTLS: {
26
+ MONTHS: 600000,
27
+ },
28
+ }));
29
+
30
+ // Mock the YNAB API
31
+ const mockYnabAPI = {
32
+ months: {
33
+ getBudgetMonth: vi.fn(),
34
+ getBudgetMonths: vi.fn(),
35
+ },
36
+ } as unknown as ynab.API;
37
+
38
+ // Import mocked cache manager
39
+ const { cacheManager, CacheManager, CACHE_TTLS } = await import('../../server/cacheManager.js');
40
+
41
+ describe('Month Tools', () => {
42
+ beforeEach(() => {
43
+ vi.resetAllMocks();
44
+ // Reset NODE_ENV to test to ensure cache bypassing in tests
45
+ process.env['NODE_ENV'] = 'test';
46
+ // Mock cache.wrap to call the loader directly (bypass cache)
47
+ (cacheManager.wrap as ReturnType<typeof vi.fn>).mockImplementation(
48
+ async (
49
+ _key: string,
50
+ options: {
51
+ loader: () => Promise<unknown>;
52
+ },
53
+ ) => {
54
+ return await options.loader();
55
+ },
56
+ );
57
+ (cacheManager.has as ReturnType<typeof vi.fn>).mockReturnValue(false);
58
+ (CacheManager.generateKey as ReturnType<typeof vi.fn>).mockImplementation(
59
+ (prefix: string, ...parts: (string | number | boolean | undefined)[]) =>
60
+ [prefix, ...parts.filter((part) => part !== undefined)].join(':'),
61
+ );
62
+ });
63
+
64
+ describe('handleGetMonth', () => {
65
+ it('should bypass cache in test environment', async () => {
66
+ const mockMonth = {
67
+ month: '2024-01-01',
68
+ note: 'January budget',
69
+ income: 500000,
70
+ budgeted: 450000,
71
+ activity: -400000,
72
+ to_be_budgeted: 50000,
73
+ age_of_money: 30,
74
+ deleted: false,
75
+ categories: [],
76
+ };
77
+
78
+ (mockYnabAPI.months.getBudgetMonth as any).mockResolvedValue({
79
+ data: { month: mockMonth },
80
+ });
81
+
82
+ const result = await handleGetMonth(mockYnabAPI, {
83
+ budget_id: 'budget-1',
84
+ month: '2024-01-01',
85
+ });
86
+
87
+ // Verify the mock cache was called (and bypassed to call the loader)
88
+ expect(cacheManager.wrap).toHaveBeenCalled();
89
+ expect(mockYnabAPI.months.getBudgetMonth).toHaveBeenCalledTimes(1);
90
+
91
+ const parsedContent = JSON.parse(result.content[0].text);
92
+ expect(parsedContent.cached).toBe(false);
93
+ expect(parsedContent.cache_info).toBe('Fresh data retrieved from YNAB API');
94
+ expect(parsedContent.month.month).toBe('2024-01-01');
95
+ });
96
+
97
+ it.skip('should use cache when NODE_ENV is not test - obsolete test, caching now handled by DeltaFetcher', async () => {
98
+ // Temporarily set NODE_ENV to non-test
99
+ process.env['NODE_ENV'] = 'development';
100
+
101
+ const mockMonth = {
102
+ month: '2024-01-01',
103
+ note: 'January budget',
104
+ income: 500000,
105
+ budgeted: 450000,
106
+ activity: -400000,
107
+ to_be_budgeted: 50000,
108
+ age_of_money: 30,
109
+ deleted: false,
110
+ categories: [],
111
+ };
112
+
113
+ const mockCacheKey = 'month:get:budget-1:2024-01-01:generated-key';
114
+ (CacheManager.generateKey as any).mockReturnValue(mockCacheKey);
115
+ (cacheManager.wrap as any).mockResolvedValue(mockMonth);
116
+ (cacheManager.has as any).mockReturnValue(true);
117
+
118
+ const result = await handleGetMonth(mockYnabAPI, {
119
+ budget_id: 'budget-1',
120
+ month: '2024-01-01',
121
+ });
122
+
123
+ const parsedContent = JSON.parse(result.content[0].text);
124
+ expect(parsedContent.cached).toBe(true);
125
+ expect(parsedContent.cache_info).toBe('Data retrieved from cache for improved performance');
126
+
127
+ // Verify cache was used
128
+ expect(CacheManager.generateKey).toHaveBeenCalledWith(
129
+ 'month',
130
+ 'get',
131
+ 'budget-1',
132
+ '2024-01-01',
133
+ );
134
+ expect(cacheManager.wrap).toHaveBeenCalledWith(mockCacheKey, {
135
+ ttl: CACHE_TTLS.MONTHS,
136
+ loader: expect.any(Function),
137
+ });
138
+ expect(cacheManager.has).toHaveBeenCalledWith(mockCacheKey);
139
+
140
+ // Reset NODE_ENV
141
+ process.env['NODE_ENV'] = 'test';
142
+ });
143
+
144
+ it('should return formatted month data on success', async () => {
145
+ const mockMonth = {
146
+ month: '2024-01-01',
147
+ note: 'January budget',
148
+ income: 500000,
149
+ budgeted: 450000,
150
+ activity: -400000,
151
+ to_be_budgeted: 50000,
152
+ age_of_money: 30,
153
+ deleted: false,
154
+ categories: [
155
+ {
156
+ id: 'category-1',
157
+ category_group_id: 'group-1',
158
+ category_group_name: 'Monthly Bills',
159
+ name: 'Rent',
160
+ hidden: false,
161
+ original_category_group_id: null,
162
+ note: 'Monthly rent payment',
163
+ budgeted: 150000,
164
+ activity: -150000,
165
+ balance: 0,
166
+ goal_type: 'TB',
167
+ goal_creation_month: '2024-01-01',
168
+ goal_target: 150000,
169
+ goal_target_month: '2024-01-01',
170
+ goal_percentage_complete: 100,
171
+ goal_months_to_budget: 0,
172
+ goal_under_funded: 0,
173
+ goal_overall_funded: 150000,
174
+ goal_overall_left: 0,
175
+ deleted: false,
176
+ },
177
+ ],
178
+ };
179
+
180
+ (mockYnabAPI.months.getBudgetMonth as any).mockResolvedValue({
181
+ data: { month: mockMonth },
182
+ });
183
+
184
+ const result = await handleGetMonth(mockYnabAPI, {
185
+ budget_id: 'budget-1',
186
+ month: '2024-01-01',
187
+ });
188
+
189
+ expect(result.content).toHaveLength(1);
190
+ expect(result.content[0].type).toBe('text');
191
+
192
+ const parsedContent = JSON.parse(result.content[0].text);
193
+ expect(parsedContent.month.month).toBe('2024-01-01');
194
+ expect(parsedContent.month.note).toBe('January budget');
195
+ expect(parsedContent.month.income).toBe(500);
196
+ expect(parsedContent.month.budgeted).toBe(450);
197
+ expect(parsedContent.month.activity).toBe(-400);
198
+ expect(parsedContent.month.to_be_budgeted).toBe(50);
199
+ expect(parsedContent.month.age_of_money).toBe(30);
200
+ expect(parsedContent.month.categories).toHaveLength(1);
201
+ expect(parsedContent.month.categories[0].name).toBe('Rent');
202
+ });
203
+
204
+ it('should handle 401 authentication errors', async () => {
205
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(new Error('401 Unauthorized'));
206
+
207
+ const result = await handleGetMonth(mockYnabAPI, {
208
+ budget_id: 'budget-1',
209
+ month: '2024-01-01',
210
+ });
211
+
212
+ expect(result.content).toHaveLength(1);
213
+ const parsedContent = JSON.parse(result.content[0].text);
214
+ expect(parsedContent.error.message).toBe('Invalid or expired YNAB access token');
215
+ });
216
+
217
+ it('should handle 403 forbidden errors', async () => {
218
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(new Error('403 Forbidden'));
219
+
220
+ const result = await handleGetMonth(mockYnabAPI, {
221
+ budget_id: 'budget-1',
222
+ month: '2024-01-01',
223
+ });
224
+
225
+ expect(result.content).toHaveLength(1);
226
+ const parsedContent = JSON.parse(result.content[0].text);
227
+ expect(parsedContent.error.message).toBe('Insufficient permissions to access YNAB data');
228
+ });
229
+
230
+ it('should handle 404 not found errors', async () => {
231
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(new Error('404 Not Found'));
232
+
233
+ const result = await handleGetMonth(mockYnabAPI, {
234
+ budget_id: 'invalid-budget',
235
+ month: '2024-01-01',
236
+ });
237
+
238
+ expect(result.content).toHaveLength(1);
239
+ const parsedContent = JSON.parse(result.content[0].text);
240
+ expect(parsedContent.error.message).toBe('Budget or month not found');
241
+ });
242
+
243
+ it('should handle 429 rate limit errors', async () => {
244
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(
245
+ new Error('429 Too Many Requests'),
246
+ );
247
+
248
+ const result = await handleGetMonth(mockYnabAPI, {
249
+ budget_id: 'budget-1',
250
+ month: '2024-01-01',
251
+ });
252
+
253
+ expect(result.content).toHaveLength(1);
254
+ const parsedContent = JSON.parse(result.content[0].text);
255
+ expect(parsedContent.error.message).toBe('Rate limit exceeded. Please try again later');
256
+ });
257
+
258
+ it('should handle 500 server errors', async () => {
259
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(
260
+ new Error('500 Internal Server Error'),
261
+ );
262
+
263
+ const result = await handleGetMonth(mockYnabAPI, {
264
+ budget_id: 'budget-1',
265
+ month: '2024-01-01',
266
+ });
267
+
268
+ expect(result.content).toHaveLength(1);
269
+ const parsedContent = JSON.parse(result.content[0].text);
270
+ expect(parsedContent.error.message).toBe('YNAB service is currently unavailable');
271
+ });
272
+
273
+ it('should handle generic errors', async () => {
274
+ (mockYnabAPI.months.getBudgetMonth as any).mockRejectedValue(new Error('Network error'));
275
+
276
+ const result = await handleGetMonth(mockYnabAPI, {
277
+ budget_id: 'budget-1',
278
+ month: '2024-01-01',
279
+ });
280
+
281
+ expect(result.content).toHaveLength(1);
282
+ const parsedContent = JSON.parse(result.content[0].text);
283
+ expect(parsedContent.error.message).toBe('Failed to get month data');
284
+ });
285
+ });
286
+
287
+ describe('handleListMonths', () => {
288
+ it('should include cache metadata from delta fetcher results', async () => {
289
+ const mockMonths = [
290
+ {
291
+ month: '2024-01-01',
292
+ note: 'January budget',
293
+ income: 500000,
294
+ budgeted: 450000,
295
+ activity: -400000,
296
+ to_be_budgeted: 50000,
297
+ age_of_money: 30,
298
+ deleted: false,
299
+ },
300
+ ];
301
+ const { fetcher, resolved } = createDeltaFetcherMock('fetchMonths', {
302
+ data: mockMonths,
303
+ wasCached: true,
304
+ usedDelta: true,
305
+ });
306
+
307
+ const result = await handleListMonths(mockYnabAPI, fetcher, { budget_id: 'budget-1' });
308
+
309
+ const parsedContent = JSON.parse(result.content[0].text);
310
+ expect(parsedContent.cached).toBe(resolved.wasCached);
311
+ expect(parsedContent.cache_info).toContain('delta merge applied');
312
+ expect(parsedContent.months).toHaveLength(1);
313
+ });
314
+
315
+ it.skip('should use cache when NODE_ENV is not test - obsolete test, caching now handled by DeltaFetcher', async () => {
316
+ // Temporarily set NODE_ENV to non-test
317
+ process.env['NODE_ENV'] = 'development';
318
+
319
+ const mockMonths = [
320
+ {
321
+ month: '2024-01-01',
322
+ note: 'January budget',
323
+ income: 500000,
324
+ budgeted: 450000,
325
+ activity: -400000,
326
+ to_be_budgeted: 50000,
327
+ age_of_money: 30,
328
+ deleted: false,
329
+ },
330
+ ];
331
+
332
+ const mockCacheKey = 'months:list:budget-1:generated-key';
333
+ (CacheManager.generateKey as any).mockReturnValue(mockCacheKey);
334
+ (cacheManager.wrap as any).mockResolvedValue(mockMonths);
335
+ (cacheManager.has as any).mockReturnValue(true);
336
+
337
+ const result = await handleListMonths(mockYnabAPI, { budget_id: 'budget-1' });
338
+
339
+ const parsedContent = JSON.parse(result.content[0].text);
340
+ expect(parsedContent.cached).toBe(true);
341
+ expect(parsedContent.cache_info).toBe('Data retrieved from cache for improved performance');
342
+
343
+ // Verify cache was used
344
+ expect(CacheManager.generateKey).toHaveBeenCalledWith('months', 'list', 'budget-1');
345
+ expect(cacheManager.wrap).toHaveBeenCalledWith(mockCacheKey, {
346
+ ttl: CACHE_TTLS.MONTHS,
347
+ loader: expect.any(Function),
348
+ });
349
+ expect(cacheManager.has).toHaveBeenCalledWith(mockCacheKey);
350
+
351
+ // Reset NODE_ENV
352
+ process.env['NODE_ENV'] = 'test';
353
+ });
354
+
355
+ it('should return formatted months list on success', async () => {
356
+ const mockMonths = [
357
+ {
358
+ month: '2024-01-01',
359
+ note: 'January budget',
360
+ income: 500000,
361
+ budgeted: 450000,
362
+ activity: -400000,
363
+ to_be_budgeted: 50000,
364
+ age_of_money: 30,
365
+ deleted: false,
366
+ },
367
+ {
368
+ month: '2024-02-01',
369
+ note: 'February budget',
370
+ income: 520000,
371
+ budgeted: 470000,
372
+ activity: -420000,
373
+ to_be_budgeted: 50000,
374
+ age_of_money: 32,
375
+ deleted: false,
376
+ },
377
+ ];
378
+ const { fetcher, resolved } = createDeltaFetcherMock('fetchMonths', {
379
+ data: mockMonths,
380
+ wasCached: false,
381
+ });
382
+
383
+ const result = await handleListMonths(mockYnabAPI, fetcher, { budget_id: 'budget-1' });
384
+
385
+ expect(result.content).toHaveLength(1);
386
+ expect(result.content[0].type).toBe('text');
387
+
388
+ const parsedContent = JSON.parse(result.content[0].text);
389
+ expect(parsedContent.cached).toBe(resolved.wasCached);
390
+ expect(parsedContent.cache_info).toBe('Fresh data retrieved from YNAB API');
391
+ expect(parsedContent.months).toHaveLength(2);
392
+ expect(parsedContent.months[0]).toEqual({
393
+ month: '2024-01-01',
394
+ note: 'January budget',
395
+ income: 500,
396
+ budgeted: 450,
397
+ activity: -400,
398
+ to_be_budgeted: 50,
399
+ age_of_money: 30,
400
+ deleted: false,
401
+ });
402
+ expect(parsedContent.months[1]).toEqual({
403
+ month: '2024-02-01',
404
+ note: 'February budget',
405
+ income: 520,
406
+ budgeted: 470,
407
+ activity: -420,
408
+ to_be_budgeted: 50,
409
+ age_of_money: 32,
410
+ deleted: false,
411
+ });
412
+ });
413
+
414
+ it('should handle authentication errors', async () => {
415
+ const { fetcher } = createRejectingDeltaFetcherMock(
416
+ 'fetchMonths',
417
+ new Error('401 Unauthorized'),
418
+ );
419
+
420
+ const result = await handleListMonths(mockYnabAPI, fetcher, { budget_id: 'budget-1' });
421
+
422
+ expect(result.content).toHaveLength(1);
423
+ const parsedContent = JSON.parse(result.content[0].text);
424
+ expect(parsedContent.error.message).toBe('Invalid or expired YNAB access token');
425
+ });
426
+
427
+ it('should handle not found errors', async () => {
428
+ const { fetcher } = createRejectingDeltaFetcherMock(
429
+ 'fetchMonths',
430
+ new Error('404 Not Found'),
431
+ );
432
+
433
+ const result = await handleListMonths(mockYnabAPI, fetcher, { budget_id: 'invalid-budget' });
434
+
435
+ expect(result.content).toHaveLength(1);
436
+ const parsedContent = JSON.parse(result.content[0].text);
437
+ expect(parsedContent.error.message).toBe('Budget or month not found');
438
+ });
439
+
440
+ it('should handle generic errors', async () => {
441
+ (mockYnabAPI.months.getBudgetMonths as any).mockRejectedValue(new Error('Network error'));
442
+
443
+ const result = await handleListMonths(mockYnabAPI, { budget_id: 'budget-1' });
444
+
445
+ expect(result.content).toHaveLength(1);
446
+ const parsedContent = JSON.parse(result.content[0].text);
447
+ expect(parsedContent.error.message).toBe('Failed to list months');
448
+ });
449
+ });
450
+
451
+ describe('GetMonthSchema', () => {
452
+ it('should validate valid parameters', () => {
453
+ const result = GetMonthSchema.parse({
454
+ budget_id: 'valid-budget-id',
455
+ month: '2024-01-01',
456
+ });
457
+ expect(result.budget_id).toBe('valid-budget-id');
458
+ expect(result.month).toBe('2024-01-01');
459
+ });
460
+
461
+ it('should reject empty budget_id', () => {
462
+ expect(() =>
463
+ GetMonthSchema.parse({
464
+ budget_id: '',
465
+ month: '2024-01-01',
466
+ }),
467
+ ).toThrow();
468
+ });
469
+
470
+ it('should reject missing budget_id', () => {
471
+ expect(() =>
472
+ GetMonthSchema.parse({
473
+ month: '2024-01-01',
474
+ }),
475
+ ).toThrow();
476
+ });
477
+
478
+ it('should reject invalid month format', () => {
479
+ expect(() =>
480
+ GetMonthSchema.parse({
481
+ budget_id: 'valid-budget-id',
482
+ month: '2024-1-1',
483
+ }),
484
+ ).toThrow();
485
+ });
486
+
487
+ it('should reject missing month', () => {
488
+ expect(() =>
489
+ GetMonthSchema.parse({
490
+ budget_id: 'valid-budget-id',
491
+ }),
492
+ ).toThrow();
493
+ });
494
+
495
+ it('should reject non-ISO date format', () => {
496
+ expect(() =>
497
+ GetMonthSchema.parse({
498
+ budget_id: 'valid-budget-id',
499
+ month: '01/01/2024',
500
+ }),
501
+ ).toThrow();
502
+ });
503
+ });
504
+
505
+ describe('ListMonthsSchema', () => {
506
+ it('should validate valid budget_id', () => {
507
+ const result = ListMonthsSchema.parse({ budget_id: 'valid-budget-id' });
508
+ expect(result.budget_id).toBe('valid-budget-id');
509
+ });
510
+
511
+ it('should reject empty budget_id', () => {
512
+ expect(() => ListMonthsSchema.parse({ budget_id: '' })).toThrow();
513
+ });
514
+
515
+ it('should reject missing budget_id', () => {
516
+ expect(() => ListMonthsSchema.parse({})).toThrow();
517
+ });
518
+
519
+ it('should reject non-string budget_id', () => {
520
+ expect(() => ListMonthsSchema.parse({ budget_id: 123 })).toThrow();
521
+ });
522
+ });
523
+ });
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
2
+ import * as ynab from 'ynab';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4
+ import { handleListPayees } from '../payeeTools.js';
5
+ import { CacheManager } from '../../server/cacheManager.js';
6
+ import { ServerKnowledgeStore } from '../../server/serverKnowledgeStore.js';
7
+ import { DeltaCache } from '../../server/deltaCache.js';
8
+ import { DeltaFetcher } from '../deltaFetcher.js';
9
+
10
+ const shouldSkip = ['true', '1', 'yes', 'y', 'on'].includes(
11
+ (process.env['SKIP_E2E_TESTS'] || '').toLowerCase().trim(),
12
+ );
13
+ const hasToken = !!process.env['YNAB_ACCESS_TOKEN'];
14
+ const skipTests = shouldSkip || !hasToken;
15
+ const describeIntegration = skipTests ? describe.skip : describe;
16
+
17
+ describeIntegration('Delta-backed payee tool handler', () => {
18
+ let ynabAPI: ynab.API;
19
+ let testBudgetId: string;
20
+ let deltaFetcher: DeltaFetcher;
21
+ let previousNodeEnv: string | undefined;
22
+
23
+ beforeAll(async () => {
24
+ const accessToken = process.env['YNAB_ACCESS_TOKEN']!;
25
+ ynabAPI = new ynab.API(accessToken);
26
+ const budgetsResponse = await ynabAPI.budgets.getBudgets();
27
+ const budget = budgetsResponse.data.budgets[0];
28
+ if (!budget) {
29
+ throw new Error('No budgets available for delta integration tests.');
30
+ }
31
+ testBudgetId = budget.id;
32
+ });
33
+
34
+ beforeEach(() => {
35
+ previousNodeEnv = process.env['NODE_ENV'];
36
+ process.env['NODE_ENV'] = 'integration';
37
+ const cacheManager = new CacheManager();
38
+ const knowledgeStore = new ServerKnowledgeStore();
39
+ const deltaCache = new DeltaCache(cacheManager, knowledgeStore);
40
+ deltaFetcher = new DeltaFetcher(ynabAPI, deltaCache);
41
+ process.env['YNAB_MCP_ENABLE_DELTA'] = 'true';
42
+ });
43
+
44
+ afterEach(() => {
45
+ delete process.env['YNAB_MCP_ENABLE_DELTA'];
46
+ if (previousNodeEnv === undefined) {
47
+ delete process.env['NODE_ENV'];
48
+ } else {
49
+ process.env['NODE_ENV'] = previousNodeEnv;
50
+ }
51
+ previousNodeEnv = undefined;
52
+ });
53
+
54
+ const parseResponse = (result: CallToolResult) => {
55
+ const content = result.content?.[0];
56
+ if (!content || content.type !== 'text') {
57
+ throw new Error('Unexpected tool response format');
58
+ }
59
+ return JSON.parse(content.text);
60
+ };
61
+ const expectCacheHit = (payload: { cached: boolean; cache_info: string }) => {
62
+ expect(payload.cached).toBe(true);
63
+ expect(payload.cache_info).toMatch(/cache/i);
64
+ };
65
+
66
+ it(
67
+ 'serves cached payee results on the second invocation',
68
+ { meta: { tier: 'domain', domain: 'delta' } },
69
+ async () => {
70
+ const params = { budget_id: testBudgetId };
71
+ const firstCall = await handleListPayees(ynabAPI, deltaFetcher, params);
72
+ const firstPayload = parseResponse(firstCall);
73
+ expect(firstPayload.cached).toBe(false);
74
+
75
+ const secondCall = await handleListPayees(ynabAPI, deltaFetcher, params);
76
+ const secondPayload = parseResponse(secondCall);
77
+ expectCacheHit(secondPayload);
78
+ },
79
+ );
80
+ });