@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,112 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import * as ynab from 'ynab';
3
+ import { z } from 'zod/v4';
4
+ import { withToolErrorHandling } from '../types/index.js';
5
+ import { responseFormatter } from '../server/responseFormatter.js';
6
+ import type { DeltaFetcher } from './deltaFetcher.js';
7
+ import { resolveDeltaFetcherArgs } from './deltaSupport.js';
8
+
9
+ /**
10
+ * Schema for ynab:get_budget tool parameters
11
+ */
12
+ export const GetBudgetSchema = z
13
+ .object({
14
+ budget_id: z.string().min(1, 'Budget ID is required'),
15
+ })
16
+ .strict();
17
+
18
+ export type GetBudgetParams = z.infer<typeof GetBudgetSchema>;
19
+
20
+ /**
21
+ * Handles the ynab:list_budgets tool call
22
+ * Lists all budgets associated with the user's account
23
+ */
24
+ export async function handleListBudgets(
25
+ ynabAPI: ynab.API,
26
+ deltaFetcherOrParams?: DeltaFetcher | Record<string, unknown>,
27
+ maybeParams?: Record<string, unknown>,
28
+ ): Promise<CallToolResult> {
29
+ const { deltaFetcher } = resolveDeltaFetcherArgs(
30
+ ynabAPI,
31
+ (deltaFetcherOrParams ?? {}) as DeltaFetcher | Record<string, unknown>,
32
+ maybeParams,
33
+ );
34
+ return await withToolErrorHandling(
35
+ async () => {
36
+ // Always use cache unless explicitly disabled
37
+ const result = await deltaFetcher.fetchBudgets();
38
+ const budgets = result.data;
39
+ const wasCached = result.wasCached;
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: responseFormatter.format({
46
+ budgets: budgets.map((budget) => ({
47
+ id: budget.id,
48
+ name: budget.name,
49
+ last_modified_on: budget.last_modified_on,
50
+ first_month: budget.first_month,
51
+ last_month: budget.last_month,
52
+ date_format: budget.date_format,
53
+ currency_format: budget.currency_format,
54
+ })),
55
+ cached: wasCached,
56
+ cache_info: wasCached
57
+ ? `Data retrieved from cache for improved performance${result.usedDelta ? ' (delta merge applied)' : ''}`
58
+ : 'Fresh data retrieved from YNAB API',
59
+ }),
60
+ },
61
+ ],
62
+ };
63
+ },
64
+ 'ynab:list_budgets',
65
+ 'listing budgets',
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Handles the ynab:get_budget tool call
71
+ * Gets detailed information for a specific budget
72
+ */
73
+ export async function handleGetBudget(
74
+ ynabAPI: ynab.API,
75
+ params: GetBudgetParams,
76
+ ): Promise<CallToolResult> {
77
+ return await withToolErrorHandling(
78
+ async () => {
79
+ const response = await ynabAPI.budgets.getBudgetById(params.budget_id);
80
+ const budget = response.data.budget;
81
+
82
+ return {
83
+ content: [
84
+ {
85
+ type: 'text',
86
+ text: responseFormatter.format({
87
+ budget: {
88
+ id: budget.id,
89
+ name: budget.name,
90
+ last_modified_on: budget.last_modified_on,
91
+ first_month: budget.first_month,
92
+ last_month: budget.last_month,
93
+ date_format: budget.date_format,
94
+ currency_format: budget.currency_format,
95
+ // Return counts instead of full arrays to avoid massive responses
96
+ accounts_count: budget.accounts?.length ?? 0,
97
+ categories_count: budget.categories?.length ?? 0,
98
+ payees_count: budget.payees?.length ?? 0,
99
+ months_count: budget.months?.length ?? 0,
100
+ // Include helpful message
101
+ message:
102
+ 'Use list_accounts, list_categories, list_payees, and list_months to get detailed lists',
103
+ },
104
+ }),
105
+ },
106
+ ],
107
+ };
108
+ },
109
+ 'ynab:get_budget',
110
+ 'getting budget details',
111
+ );
112
+ }
@@ -0,0 +1,366 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import * as ynab from 'ynab';
3
+ import { z } from 'zod/v4';
4
+ import { withToolErrorHandling } from '../types/index.js';
5
+ import { responseFormatter } from '../server/responseFormatter.js';
6
+ import { milliunitsToAmount } from '../utils/amountUtils.js';
7
+ import { cacheManager, CACHE_TTLS, CacheManager } from '../server/cacheManager.js';
8
+ import type { DeltaFetcher } from './deltaFetcher.js';
9
+ import type { DeltaCache } from '../server/deltaCache.js';
10
+ import type { ServerKnowledgeStore } from '../server/serverKnowledgeStore.js';
11
+ import { resolveDeltaFetcherArgs, resolveDeltaWriteArgs } from './deltaSupport.js';
12
+
13
+ /**
14
+ * Schema for ynab:list_categories tool parameters
15
+ */
16
+ export const ListCategoriesSchema = z
17
+ .object({
18
+ budget_id: z.string().min(1, 'Budget ID is required'),
19
+ })
20
+ .strict();
21
+
22
+ export type ListCategoriesParams = z.infer<typeof ListCategoriesSchema>;
23
+
24
+ /**
25
+ * Schema for ynab:get_category tool parameters
26
+ */
27
+ export const GetCategorySchema = z
28
+ .object({
29
+ budget_id: z.string().min(1, 'Budget ID is required'),
30
+ category_id: z.string().min(1, 'Category ID is required'),
31
+ })
32
+ .strict();
33
+
34
+ export type GetCategoryParams = z.infer<typeof GetCategorySchema>;
35
+
36
+ /**
37
+ * Schema for ynab:update_category tool parameters
38
+ */
39
+ export const UpdateCategorySchema = z
40
+ .object({
41
+ budget_id: z.string().min(1, 'Budget ID is required'),
42
+ category_id: z.string().min(1, 'Category ID is required'),
43
+ budgeted: z.number().int('Budgeted amount must be an integer in milliunits'),
44
+ dry_run: z.boolean().optional(),
45
+ })
46
+ .strict();
47
+
48
+ export type UpdateCategoryParams = z.infer<typeof UpdateCategorySchema>;
49
+
50
+ /**
51
+ * Convert goal-related monetary fields from milliunits to dollars.
52
+ * Returns an object with the four converted goal fields.
53
+ */
54
+ function convertGoalFields(category: ynab.Category) {
55
+ return {
56
+ goal_target:
57
+ category.goal_target != null ? milliunitsToAmount(category.goal_target) : undefined,
58
+ goal_under_funded:
59
+ category.goal_under_funded != null
60
+ ? milliunitsToAmount(category.goal_under_funded)
61
+ : undefined,
62
+ goal_overall_funded:
63
+ category.goal_overall_funded != null
64
+ ? milliunitsToAmount(category.goal_overall_funded)
65
+ : undefined,
66
+ goal_overall_left:
67
+ category.goal_overall_left != null
68
+ ? milliunitsToAmount(category.goal_overall_left)
69
+ : undefined,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Handles the ynab:list_categories tool call
75
+ * Lists all categories for a specific budget
76
+ */
77
+ export async function handleListCategories(
78
+ ynabAPI: ynab.API,
79
+ deltaFetcher: DeltaFetcher,
80
+ params: ListCategoriesParams,
81
+ ): Promise<CallToolResult>;
82
+ export async function handleListCategories(
83
+ ynabAPI: ynab.API,
84
+ params: ListCategoriesParams,
85
+ ): Promise<CallToolResult>;
86
+ export async function handleListCategories(
87
+ ynabAPI: ynab.API,
88
+ deltaFetcherOrParams: DeltaFetcher | ListCategoriesParams,
89
+ maybeParams?: ListCategoriesParams,
90
+ ): Promise<CallToolResult> {
91
+ const { deltaFetcher, params } = resolveDeltaFetcherArgs(
92
+ ynabAPI,
93
+ deltaFetcherOrParams,
94
+ maybeParams,
95
+ );
96
+ return await withToolErrorHandling(
97
+ async () => {
98
+ const result = await deltaFetcher.fetchCategories(params.budget_id);
99
+ const categoryGroups = result.data;
100
+ const wasCached = result.wasCached;
101
+
102
+ // Flatten categories from all category groups
103
+ const allCategories = categoryGroups.flatMap((group) =>
104
+ group.categories.map((category) => ({
105
+ id: category.id,
106
+ category_group_id: category.category_group_id,
107
+ category_group_name: group.name,
108
+ name: category.name,
109
+ hidden: category.hidden,
110
+ original_category_group_id: category.original_category_group_id,
111
+ note: category.note,
112
+ budgeted: milliunitsToAmount(category.budgeted),
113
+ activity: milliunitsToAmount(category.activity),
114
+ balance: milliunitsToAmount(category.balance),
115
+ goal_type: category.goal_type,
116
+ goal_creation_month: category.goal_creation_month,
117
+ ...convertGoalFields(category),
118
+ goal_target_month: category.goal_target_month,
119
+ goal_percentage_complete: category.goal_percentage_complete,
120
+ })),
121
+ );
122
+
123
+ return {
124
+ content: [
125
+ {
126
+ type: 'text',
127
+ text: responseFormatter.format({
128
+ categories: allCategories,
129
+ category_groups: categoryGroups.map((group) => ({
130
+ id: group.id,
131
+ name: group.name,
132
+ hidden: group.hidden,
133
+ deleted: group.deleted,
134
+ })),
135
+ cached: wasCached,
136
+ cache_info: wasCached
137
+ ? `Data retrieved from cache for improved performance${result.usedDelta ? ' (delta merge applied)' : ''}`
138
+ : 'Fresh data retrieved from YNAB API',
139
+ }),
140
+ },
141
+ ],
142
+ };
143
+ },
144
+ 'ynab:list_categories',
145
+ 'listing categories',
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Handles the ynab:get_category tool call
151
+ * Gets detailed information for a specific category
152
+ */
153
+ export async function handleGetCategory(
154
+ ynabAPI: ynab.API,
155
+ params: GetCategoryParams,
156
+ ): Promise<CallToolResult> {
157
+ return await withToolErrorHandling(
158
+ async () => {
159
+ // Use enhanced CacheManager wrap method
160
+ const cacheKey = CacheManager.generateKey(
161
+ 'category',
162
+ 'get',
163
+ params.budget_id,
164
+ params.category_id,
165
+ );
166
+ const wasCached = cacheManager.has(cacheKey);
167
+ const category = await cacheManager.wrap<ynab.Category>(cacheKey, {
168
+ ttl: CACHE_TTLS.CATEGORIES,
169
+ loader: async () => {
170
+ const response = await ynabAPI.categories.getCategoryById(
171
+ params.budget_id,
172
+ params.category_id,
173
+ );
174
+ return response.data.category;
175
+ },
176
+ });
177
+
178
+ return {
179
+ content: [
180
+ {
181
+ type: 'text',
182
+ text: responseFormatter.format({
183
+ category: {
184
+ id: category.id,
185
+ category_group_id: category.category_group_id,
186
+ name: category.name,
187
+ hidden: category.hidden,
188
+ original_category_group_id: category.original_category_group_id,
189
+ note: category.note,
190
+ budgeted: milliunitsToAmount(category.budgeted),
191
+ activity: milliunitsToAmount(category.activity),
192
+ balance: milliunitsToAmount(category.balance),
193
+ goal_type: category.goal_type,
194
+ goal_creation_month: category.goal_creation_month,
195
+ ...convertGoalFields(category),
196
+ goal_target_month: category.goal_target_month,
197
+ goal_percentage_complete: category.goal_percentage_complete,
198
+ },
199
+ cached: wasCached,
200
+ cache_info: wasCached
201
+ ? 'Data retrieved from cache for improved performance'
202
+ : 'Fresh data retrieved from YNAB API',
203
+ }),
204
+ },
205
+ ],
206
+ };
207
+ },
208
+ 'ynab:get_category',
209
+ 'getting category',
210
+ );
211
+ }
212
+
213
+ /**
214
+ * Handles the ynab:update_category tool call
215
+ * Updates the budgeted amount for a category in the current month
216
+ */
217
+ export async function handleUpdateCategory(
218
+ ynabAPI: ynab.API,
219
+ deltaCache: DeltaCache,
220
+ knowledgeStore: ServerKnowledgeStore,
221
+ params: UpdateCategoryParams,
222
+ ): Promise<CallToolResult>;
223
+ export async function handleUpdateCategory(
224
+ ynabAPI: ynab.API,
225
+ params: UpdateCategoryParams,
226
+ ): Promise<CallToolResult>;
227
+ export async function handleUpdateCategory(
228
+ ynabAPI: ynab.API,
229
+ deltaCacheOrParams: DeltaCache | UpdateCategoryParams,
230
+ knowledgeStoreOrParams?: ServerKnowledgeStore | UpdateCategoryParams,
231
+ maybeParams?: UpdateCategoryParams,
232
+ ): Promise<CallToolResult> {
233
+ const { deltaCache, knowledgeStore, params } = resolveDeltaWriteArgs(
234
+ deltaCacheOrParams,
235
+ knowledgeStoreOrParams,
236
+ maybeParams,
237
+ );
238
+ try {
239
+ if (params.dry_run) {
240
+ const currentDate = new Date();
241
+ const currentMonth = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-01`;
242
+ return {
243
+ content: [
244
+ {
245
+ type: 'text',
246
+ text: responseFormatter.format({
247
+ dry_run: true,
248
+ action: 'update_category',
249
+ request: {
250
+ budget_id: params.budget_id,
251
+ category_id: params.category_id,
252
+ budgeted: milliunitsToAmount(params.budgeted),
253
+ month: currentMonth,
254
+ },
255
+ }),
256
+ },
257
+ ],
258
+ };
259
+ }
260
+ // Get current month in YNAB format (YYYY-MM-01)
261
+ const currentDate = new Date();
262
+ const currentMonth = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-01`;
263
+
264
+ const response = await ynabAPI.categories.updateMonthCategory(
265
+ params.budget_id,
266
+ currentMonth,
267
+ params.category_id,
268
+ { category: { budgeted: params.budgeted } },
269
+ );
270
+
271
+ const category = response.data.category;
272
+
273
+ // Invalidate category-related caches after successful update
274
+ const categoriesListCacheKey = CacheManager.generateKey('categories', 'list', params.budget_id);
275
+ const specificCategoryCacheKey = CacheManager.generateKey(
276
+ 'category',
277
+ 'get',
278
+ params.budget_id,
279
+ params.category_id,
280
+ );
281
+ cacheManager.delete(categoriesListCacheKey);
282
+ cacheManager.delete(specificCategoryCacheKey);
283
+
284
+ // Invalidate month-related caches as category budget changes affect month data
285
+ const monthsListCacheKey = CacheManager.generateKey('months', 'list', params.budget_id);
286
+ const currentMonthCacheKey = CacheManager.generateKey(
287
+ 'month',
288
+ 'get',
289
+ params.budget_id,
290
+ currentMonth,
291
+ );
292
+ cacheManager.delete(monthsListCacheKey);
293
+ cacheManager.delete(currentMonthCacheKey);
294
+
295
+ deltaCache.invalidate(params.budget_id, 'categories');
296
+ deltaCache.invalidate(params.budget_id, 'months');
297
+ const serverKnowledge = response.data.server_knowledge;
298
+ if (typeof serverKnowledge === 'number') {
299
+ knowledgeStore.update(categoriesListCacheKey, serverKnowledge);
300
+ knowledgeStore.update(monthsListCacheKey, serverKnowledge);
301
+ }
302
+
303
+ return {
304
+ content: [
305
+ {
306
+ type: 'text',
307
+ text: responseFormatter.format({
308
+ category: {
309
+ id: category.id,
310
+ category_group_id: category.category_group_id,
311
+ name: category.name,
312
+ hidden: category.hidden,
313
+ original_category_group_id: category.original_category_group_id,
314
+ note: category.note,
315
+ budgeted: milliunitsToAmount(category.budgeted),
316
+ activity: milliunitsToAmount(category.activity),
317
+ balance: milliunitsToAmount(category.balance),
318
+ goal_type: category.goal_type,
319
+ goal_creation_month: category.goal_creation_month,
320
+ ...convertGoalFields(category),
321
+ goal_target_month: category.goal_target_month,
322
+ goal_percentage_complete: category.goal_percentage_complete,
323
+ },
324
+ updated_month: currentMonth,
325
+ }),
326
+ },
327
+ ],
328
+ };
329
+ } catch (error) {
330
+ return handleCategoryError(error, 'Failed to update category');
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Handles errors from category-related API calls
336
+ */
337
+ function handleCategoryError(error: unknown, defaultMessage: string): CallToolResult {
338
+ let errorMessage = defaultMessage;
339
+
340
+ if (error instanceof Error) {
341
+ if (error.message.includes('401') || error.message.includes('Unauthorized')) {
342
+ errorMessage = 'Invalid or expired YNAB access token';
343
+ } else if (error.message.includes('403') || error.message.includes('Forbidden')) {
344
+ errorMessage = 'Insufficient permissions to access YNAB data';
345
+ } else if (error.message.includes('404') || error.message.includes('Not Found')) {
346
+ errorMessage = 'Budget or category not found';
347
+ } else if (error.message.includes('429') || error.message.includes('Too Many Requests')) {
348
+ errorMessage = 'Rate limit exceeded. Please try again later';
349
+ } else if (error.message.includes('500') || error.message.includes('Internal Server Error')) {
350
+ errorMessage = 'YNAB service is currently unavailable';
351
+ }
352
+ }
353
+
354
+ return {
355
+ content: [
356
+ {
357
+ type: 'text',
358
+ text: responseFormatter.format({
359
+ error: {
360
+ message: errorMessage,
361
+ },
362
+ }),
363
+ },
364
+ ],
365
+ };
366
+ }
@@ -0,0 +1,163 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import * as ynab from 'ynab';
3
+ import { responseFormatter } from '../../server/responseFormatter.js';
4
+ import { BankTransaction, YNABTransaction, TransactionMatch } from './types.js';
5
+
6
+ /**
7
+ * Find suggested payee for unmatched bank transaction
8
+ */
9
+ export function findSuggestedPayee(
10
+ description: string,
11
+ payees: ynab.Payee[],
12
+ ): { suggested_payee_id?: string; suggested_payee_name?: string; suggestion_reason?: string } {
13
+ if (!description) {
14
+ return {};
15
+ }
16
+
17
+ const lower_description = description.toLowerCase();
18
+
19
+ // Simple search: check if payee name is contained in the description
20
+ for (const payee of payees) {
21
+ const lower_payee_name = payee.name.toLowerCase();
22
+ if (lower_description.includes(lower_payee_name)) {
23
+ return {
24
+ suggested_payee_id: payee.id,
25
+ suggested_payee_name: payee.name,
26
+ suggestion_reason: `Matched payee '${payee.name}' in description.`,
27
+ };
28
+ }
29
+ }
30
+
31
+ // If no match, suggest the original description as the new payee name (cleaned up a bit)
32
+ const suggested_name = description
33
+ .replace(/\d+/g, '') // Remove numbers
34
+ .replace(/\s+/g, ' ') // Consolidate whitespace
35
+ .trim();
36
+
37
+ return {
38
+ suggested_payee_name: suggested_name,
39
+ suggestion_reason: `No matching payee found. Suggested new payee name from description.`,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Build summary statistics for the comparison
45
+ */
46
+ export function buildSummary(
47
+ bankTransactions: BankTransaction[],
48
+ ynabTransactions: YNABTransaction[],
49
+ matches: TransactionMatch[],
50
+ unmatchedBank: BankTransaction[],
51
+ unmatchedYnab: YNABTransaction[],
52
+ parameters: { amount_tolerance?: number; date_tolerance_days?: number },
53
+ dateRange: { start: string; end: string },
54
+ ) {
55
+ return {
56
+ bank_transactions_count: bankTransactions.length,
57
+ ynab_transactions_count: ynabTransactions.length,
58
+ matches_found: matches.length,
59
+ missing_in_ynab: unmatchedBank.length,
60
+ missing_in_bank: unmatchedYnab.length,
61
+ date_range: dateRange,
62
+ parameters: {
63
+ amount_tolerance: parameters.amount_tolerance,
64
+ date_tolerance_days: parameters.date_tolerance_days,
65
+ },
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Format matched transaction pairs
71
+ */
72
+ export function formatMatches(matches: TransactionMatch[]) {
73
+ return matches.map((match) => ({
74
+ bank_date: match.bank_transaction.date.toISOString().split('T')[0],
75
+ bank_amount: (match.bank_transaction.amount / 1000).toFixed(2),
76
+ bank_description: match.bank_transaction.description,
77
+ ynab_date: match.ynab_transaction.date.toISOString().split('T')[0],
78
+ ynab_amount: (match.ynab_transaction.amount / 1000).toFixed(2),
79
+ ynab_payee: match.ynab_transaction.payee_name,
80
+ ynab_transaction: {
81
+ id: match.ynab_transaction.id,
82
+ cleared: match.ynab_transaction.cleared,
83
+ },
84
+ match_score: match.match_score,
85
+ match_reasons: match.match_reasons,
86
+ }));
87
+ }
88
+
89
+ /**
90
+ * Format unmatched bank transactions with payee suggestions
91
+ */
92
+ export function formatUnmatchedBank(unmatchedBank: BankTransaction[], payees: ynab.Payee[]) {
93
+ return unmatchedBank.map((txn) => {
94
+ const payeeSuggestion = findSuggestedPayee(txn.description, payees);
95
+ return {
96
+ date: txn.date.toISOString().split('T')[0],
97
+ amount: (txn.amount / 1000).toFixed(2),
98
+ description: txn.description,
99
+ row_number: txn.row_number,
100
+ ...payeeSuggestion,
101
+ };
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Format unmatched YNAB transactions
107
+ */
108
+ export function formatUnmatchedYNAB(unmatchedYnab: YNABTransaction[]) {
109
+ return unmatchedYnab.map((txn) => ({
110
+ id: txn.id,
111
+ date: txn.date.toISOString().split('T')[0],
112
+ amount: (txn.amount / 1000).toFixed(2),
113
+ payee_name: txn.payee_name,
114
+ memo: txn.memo,
115
+ cleared: txn.cleared,
116
+ }));
117
+ }
118
+
119
+ /**
120
+ * Build the complete comparison result
121
+ */
122
+ export function buildComparisonResult(
123
+ matchResults: {
124
+ matches: TransactionMatch[];
125
+ unmatched_bank: BankTransaction[];
126
+ unmatched_ynab: YNABTransaction[];
127
+ },
128
+ bankTransactions: BankTransaction[],
129
+ ynabTransactions: YNABTransaction[],
130
+ payees: ynab.Payee[],
131
+ parameters: { amount_tolerance?: number; date_tolerance_days?: number },
132
+ dateRange: { start: string; end: string },
133
+ ): CallToolResult {
134
+ const { matches, unmatched_bank, unmatched_ynab } = matchResults;
135
+
136
+ const summary = buildSummary(
137
+ bankTransactions,
138
+ ynabTransactions,
139
+ matches,
140
+ unmatched_bank,
141
+ unmatched_ynab,
142
+ parameters,
143
+ dateRange,
144
+ );
145
+
146
+ const formattedMatches = formatMatches(matches);
147
+ const formattedUnmatchedBank = formatUnmatchedBank(unmatched_bank, payees);
148
+ const formattedUnmatchedYnab = formatUnmatchedYNAB(unmatched_ynab);
149
+
150
+ return {
151
+ content: [
152
+ {
153
+ type: 'text',
154
+ text: responseFormatter.format({
155
+ summary,
156
+ matches: formattedMatches,
157
+ missing_in_ynab: formattedUnmatchedBank,
158
+ missing_in_bank: formattedUnmatchedYnab,
159
+ }),
160
+ },
161
+ ],
162
+ };
163
+ }