@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,477 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { YNABMCPServer } from '../YNABMCPServer';
3
+ import { AuthenticationError, ConfigurationError } from '../../types/index';
4
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
+ import { skipOnRateLimit } from '../../__tests__/testUtils.js';
6
+ // StdioServerTransport import removed as it's not used in tests
7
+
8
+ /**
9
+ * Integration tests for server startup and transport setup
10
+ * Tests the complete server initialization process including:
11
+ * - Environment validation
12
+ * - YNAB API authentication
13
+ * - MCP server initialization
14
+ * - Tool registration
15
+ * - Transport connection setup
16
+ * Skips if YNAB_ACCESS_TOKEN is not set or if SKIP_E2E_TESTS is true
17
+ */
18
+ const hasToken = !!process.env['YNAB_ACCESS_TOKEN'];
19
+ const shouldSkip = process.env['SKIP_E2E_TESTS'] === 'true' || !hasToken;
20
+ const describeIntegration = shouldSkip ? describe.skip : describe;
21
+
22
+ describeIntegration('Server Startup and Transport Integration', () => {
23
+ const originalEnv = process.env;
24
+
25
+ afterEach(() => {
26
+ // Restore environment but keep API key
27
+ Object.keys(process.env).forEach((key) => {
28
+ if (key !== 'YNAB_ACCESS_TOKEN' && key !== 'YNAB_BUDGET_ID') {
29
+ if (originalEnv[key] !== undefined) {
30
+ process.env[key] = originalEnv[key];
31
+ } else {
32
+ process.env[key] = undefined;
33
+ }
34
+ }
35
+ });
36
+ });
37
+
38
+ describe('Server Initialization', () => {
39
+ it(
40
+ 'should successfully initialize server with valid configuration',
41
+ { meta: { tier: 'domain', domain: 'server' } },
42
+ () => {
43
+ const server = new YNABMCPServer(false);
44
+
45
+ expect(server).toBeInstanceOf(YNABMCPServer);
46
+ expect(server.getYNABAPI()).toBeDefined();
47
+ expect(server.getServer()).toBeInstanceOf(Server);
48
+ },
49
+ );
50
+
51
+ it(
52
+ 'should fail initialization with missing access token',
53
+ { meta: { tier: 'domain', domain: 'server' } },
54
+ () => {
55
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
56
+ delete process.env['YNAB_ACCESS_TOKEN'];
57
+
58
+ expect(() => new YNABMCPServer(false)).toThrow(ConfigurationError);
59
+ expect(() => new YNABMCPServer(false)).toThrow(
60
+ 'YNAB_ACCESS_TOKEN environment variable is required but not set',
61
+ );
62
+
63
+ // Restore token
64
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
65
+ },
66
+ );
67
+
68
+ it(
69
+ 'should fail initialization with invalid access token format',
70
+ { meta: { tier: 'domain', domain: 'server' } },
71
+ () => {
72
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
73
+ process.env['YNAB_ACCESS_TOKEN'] = '';
74
+
75
+ expect(() => new YNABMCPServer(false)).toThrow(ConfigurationError);
76
+ expect(() => new YNABMCPServer(false)).toThrow(
77
+ 'YNAB_ACCESS_TOKEN must be a non-empty string',
78
+ );
79
+
80
+ // Restore token
81
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
82
+ },
83
+ );
84
+ });
85
+
86
+ describe('Server Startup Validation', () => {
87
+ let server: YNABMCPServer;
88
+
89
+ beforeEach(() => {
90
+ server = new YNABMCPServer(false);
91
+ });
92
+
93
+ it(
94
+ 'should validate YNAB token during startup',
95
+ { meta: { tier: 'domain', domain: 'server' } },
96
+ async (ctx) => {
97
+ await skipOnRateLimit(async () => {
98
+ const isValid = await server.validateToken();
99
+ expect(isValid).toBe(true);
100
+ }, ctx);
101
+ },
102
+ );
103
+
104
+ it(
105
+ 'should handle invalid token gracefully during startup',
106
+ { meta: { tier: 'domain', domain: 'server' } },
107
+ async (ctx) => {
108
+ await skipOnRateLimit(async () => {
109
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
110
+ process.env['YNAB_ACCESS_TOKEN'] = 'invalid-token-12345';
111
+
112
+ try {
113
+ const invalidServer = new YNABMCPServer(false);
114
+ await expect(invalidServer.validateToken()).rejects.toThrow(AuthenticationError);
115
+ } finally {
116
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
117
+ }
118
+ }, ctx);
119
+ },
120
+ );
121
+
122
+ it(
123
+ 'should provide detailed error messages for authentication failures',
124
+ { meta: { tier: 'domain', domain: 'server' } },
125
+ async (ctx) => {
126
+ await skipOnRateLimit(async () => {
127
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
128
+ process.env['YNAB_ACCESS_TOKEN'] = 'definitely-invalid-token';
129
+
130
+ try {
131
+ const invalidServer = new YNABMCPServer(false);
132
+ await expect(invalidServer.validateToken()).rejects.toThrow(AuthenticationError);
133
+
134
+ // Verify the error message contains relevant information
135
+ try {
136
+ await invalidServer.validateToken();
137
+ } catch (error) {
138
+ expect(error).toBeInstanceOf(AuthenticationError);
139
+ expect(error.message).toContain('Token validation failed');
140
+ }
141
+ } finally {
142
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
143
+ }
144
+ }, ctx);
145
+ },
146
+ );
147
+ });
148
+
149
+ describe('Tool Registration', () => {
150
+ let server: YNABMCPServer;
151
+
152
+ beforeEach(() => {
153
+ server = new YNABMCPServer(false);
154
+ });
155
+
156
+ it(
157
+ 'should register all expected YNAB tools',
158
+ { meta: { tier: 'domain', domain: 'server' } },
159
+ async () => {
160
+ const mcpServer = server.getServer();
161
+
162
+ // We can't directly call the handler, but we can verify the server has the right structure
163
+ expect(mcpServer).toBeDefined();
164
+
165
+ // Verify the server instance has been properly initialized
166
+ // The tools are registered in the constructor via setRequestHandler calls
167
+ expect(server.getYNABAPI()).toBeDefined();
168
+
169
+ // Test that the server can handle basic operations
170
+ expect(typeof server.validateToken).toBe('function');
171
+ expect(typeof server.run).toBe('function');
172
+ },
173
+ );
174
+
175
+ it(
176
+ 'should register budget management tools',
177
+ { meta: { tier: 'domain', domain: 'server' } },
178
+ () => {
179
+ // Test that the server instance includes budget tools
180
+ const mcpServer = server.getServer();
181
+ expect(mcpServer).toBeDefined();
182
+
183
+ // The tools are registered in the constructor, so if the server initializes
184
+ // successfully, the tools should be registered
185
+ expect(server.getYNABAPI().budgets).toBeDefined();
186
+ },
187
+ );
188
+
189
+ it(
190
+ 'should register account management tools',
191
+ { meta: { tier: 'domain', domain: 'server' } },
192
+ () => {
193
+ const mcpServer = server.getServer();
194
+ expect(mcpServer).toBeDefined();
195
+ expect(server.getYNABAPI().accounts).toBeDefined();
196
+ },
197
+ );
198
+
199
+ it(
200
+ 'should register transaction management tools',
201
+ { meta: { tier: 'domain', domain: 'server' } },
202
+ () => {
203
+ const mcpServer = server.getServer();
204
+ expect(mcpServer).toBeDefined();
205
+ expect(server.getYNABAPI().transactions).toBeDefined();
206
+ },
207
+ );
208
+
209
+ it(
210
+ 'should register category management tools',
211
+ { meta: { tier: 'domain', domain: 'server' } },
212
+ () => {
213
+ const mcpServer = server.getServer();
214
+ expect(mcpServer).toBeDefined();
215
+ expect(server.getYNABAPI().categories).toBeDefined();
216
+ },
217
+ );
218
+
219
+ it(
220
+ 'should register payee management tools',
221
+ { meta: { tier: 'domain', domain: 'server' } },
222
+ () => {
223
+ const mcpServer = server.getServer();
224
+ expect(mcpServer).toBeDefined();
225
+ expect(server.getYNABAPI().payees).toBeDefined();
226
+ },
227
+ );
228
+
229
+ it('should register utility tools', { meta: { tier: 'domain', domain: 'server' } }, () => {
230
+ const mcpServer = server.getServer();
231
+ expect(mcpServer).toBeDefined();
232
+ expect(server.getYNABAPI().user).toBeDefined();
233
+ });
234
+ });
235
+
236
+ describe('Transport Setup', () => {
237
+ let server: YNABMCPServer;
238
+
239
+ beforeEach(() => {
240
+ server = new YNABMCPServer(false);
241
+ });
242
+
243
+ it(
244
+ 'should attempt to connect with StdioServerTransport',
245
+ { meta: { tier: 'domain', domain: 'server' } },
246
+ async (ctx) => {
247
+ await skipOnRateLimit(async () => {
248
+ // Validate token first (this may skip if rate limited)
249
+ const isValid = await server.validateToken();
250
+ expect(isValid).toBe(true);
251
+
252
+ // If we get here, token is valid - now test transport connection
253
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
254
+ // Mock implementation for testing
255
+ });
256
+
257
+ try {
258
+ // The run method should attempt stdio connection
259
+ await server.run();
260
+
261
+ // In test environment, stdio connection will fail, but that's expected
262
+ } catch (error) {
263
+ // Expected to fail on stdio connection in test environment
264
+ // Token was already validated above, so this error should be transport-related
265
+ expect(error).not.toBeInstanceOf(ConfigurationError);
266
+ }
267
+
268
+ consoleSpy.mockRestore();
269
+ }, ctx);
270
+ },
271
+ );
272
+
273
+ it(
274
+ 'should handle transport connection errors gracefully',
275
+ { meta: { tier: 'domain', domain: 'server' } },
276
+ async (ctx) => {
277
+ await skipOnRateLimit(async () => {
278
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
279
+ // Mock implementation for testing
280
+ });
281
+
282
+ try {
283
+ await server.run();
284
+ } catch (error) {
285
+ // Should handle transport errors without crashing
286
+ expect(error).toBeDefined();
287
+ }
288
+
289
+ consoleSpy.mockRestore();
290
+ }, ctx);
291
+ },
292
+ );
293
+
294
+ it(
295
+ 'should validate token before attempting transport connection',
296
+ { meta: { tier: 'domain', domain: 'server' } },
297
+ async (ctx) => {
298
+ await skipOnRateLimit(async () => {
299
+ const validateTokenSpy = vi.spyOn(server, 'validateToken');
300
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
301
+ // Mock implementation for testing
302
+ });
303
+
304
+ try {
305
+ await server.run();
306
+ } catch {
307
+ // Transport will fail in test environment, but token validation should be called
308
+ expect(validateTokenSpy).toHaveBeenCalled();
309
+ }
310
+
311
+ validateTokenSpy.mockRestore();
312
+ consoleSpy.mockRestore();
313
+ }, ctx);
314
+ },
315
+ );
316
+ });
317
+
318
+ describe('Error Reporting', () => {
319
+ it(
320
+ 'should report configuration errors clearly',
321
+ { meta: { tier: 'domain', domain: 'server' } },
322
+ () => {
323
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
324
+ delete process.env['YNAB_ACCESS_TOKEN'];
325
+
326
+ expect(() => new YNABMCPServer(false)).toThrow(
327
+ expect.objectContaining({
328
+ message: 'YNAB_ACCESS_TOKEN environment variable is required but not set',
329
+ }),
330
+ );
331
+
332
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
333
+ },
334
+ );
335
+
336
+ it(
337
+ 'should report authentication errors clearly',
338
+ { meta: { tier: 'domain', domain: 'server' } },
339
+ async (ctx) => {
340
+ await skipOnRateLimit(async () => {
341
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
342
+ process.env['YNAB_ACCESS_TOKEN'] = 'invalid-token';
343
+
344
+ try {
345
+ const server = new YNABMCPServer(false);
346
+ await expect(server.validateToken()).rejects.toThrow(AuthenticationError);
347
+ } finally {
348
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
349
+ }
350
+ }, ctx);
351
+ },
352
+ );
353
+
354
+ it(
355
+ 'should handle startup errors without exposing sensitive information',
356
+ { meta: { tier: 'domain', domain: 'server' } },
357
+ async (ctx) => {
358
+ await skipOnRateLimit(async () => {
359
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
360
+ process.env['YNAB_ACCESS_TOKEN'] = 'invalid-token';
361
+
362
+ try {
363
+ const server = new YNABMCPServer(false);
364
+ await expect(server.run()).rejects.toThrow();
365
+
366
+ // Verify error doesn't contain the actual token
367
+ try {
368
+ await server.run();
369
+ } catch (error) {
370
+ expect(error.message).not.toContain('invalid-token');
371
+ }
372
+ } finally {
373
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
374
+ }
375
+ }, ctx);
376
+ },
377
+ );
378
+ });
379
+
380
+ describe('Graceful Shutdown', () => {
381
+ it(
382
+ 'should handle process signals gracefully',
383
+ { meta: { tier: 'domain', domain: 'server' } },
384
+ () => {
385
+ // Test that the server can be created without throwing
386
+ const server = new YNABMCPServer(false);
387
+ expect(server).toBeDefined();
388
+
389
+ // In a real scenario, the process signal handlers in index.ts would handle shutdown
390
+ // We can't easily test the actual signal handling in a unit test environment
391
+ // But we can verify the server initializes properly
392
+ },
393
+ );
394
+
395
+ it(
396
+ 'should clean up resources on shutdown',
397
+ { meta: { tier: 'domain', domain: 'server' } },
398
+ () => {
399
+ const server = new YNABMCPServer(false);
400
+
401
+ // Verify server has the necessary components for cleanup
402
+ expect(server.getServer()).toBeDefined();
403
+ expect(server.getYNABAPI()).toBeDefined();
404
+ },
405
+ );
406
+ });
407
+
408
+ describe('Full Startup Workflow', () => {
409
+ it(
410
+ 'should complete full startup sequence successfully',
411
+ { meta: { tier: 'domain', domain: 'server' } },
412
+ async (ctx) => {
413
+ await skipOnRateLimit(async () => {
414
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {
415
+ // Mock implementation for testing
416
+ });
417
+
418
+ try {
419
+ // Create server
420
+ const server = new YNABMCPServer(false);
421
+ expect(server).toBeDefined();
422
+
423
+ // Validate token
424
+ const isValid = await server.validateToken();
425
+ expect(isValid).toBe(true);
426
+
427
+ // Attempt to run (will fail on transport in test environment)
428
+ try {
429
+ await server.run();
430
+ } catch {
431
+ // Expected to fail on stdio transport in test environment
432
+ // But authentication and initialization should succeed
433
+ }
434
+
435
+ console.warn('✅ Server startup workflow completed successfully');
436
+ } finally {
437
+ consoleSpy.mockRestore();
438
+ }
439
+ }, ctx);
440
+ },
441
+ );
442
+
443
+ it(
444
+ 'should fail fast on configuration errors',
445
+ { meta: { tier: 'domain', domain: 'server' } },
446
+ () => {
447
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
448
+ delete process.env['YNAB_ACCESS_TOKEN'];
449
+
450
+ // Should fail immediately on construction, not during run()
451
+ expect(() => new YNABMCPServer(false)).toThrow(ConfigurationError);
452
+
453
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
454
+ },
455
+ );
456
+
457
+ it(
458
+ 'should fail fast on authentication errors',
459
+ { meta: { tier: 'domain', domain: 'server' } },
460
+ async (ctx) => {
461
+ await skipOnRateLimit(async () => {
462
+ const originalToken = process.env['YNAB_ACCESS_TOKEN'];
463
+ process.env['YNAB_ACCESS_TOKEN'] = 'invalid-token';
464
+
465
+ try {
466
+ const server = new YNABMCPServer(false);
467
+
468
+ // Should fail on token validation, before transport setup
469
+ await expect(server.run()).rejects.toThrow(AuthenticationError);
470
+ } finally {
471
+ process.env['YNAB_ACCESS_TOKEN'] = originalToken;
472
+ }
473
+ }, ctx);
474
+ },
475
+ );
476
+ });
477
+ });
@@ -0,0 +1,174 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { ServerKnowledgeStore } from '../serverKnowledgeStore.js';
3
+
4
+ describe('ServerKnowledgeStore', () => {
5
+ let store: ServerKnowledgeStore;
6
+
7
+ beforeEach(() => {
8
+ store = new ServerKnowledgeStore();
9
+ });
10
+
11
+ describe('Basic Operations', () => {
12
+ it('should return undefined for non-existent keys', () => {
13
+ expect(store.get('non-existent-key')).toBeUndefined();
14
+ });
15
+
16
+ it('should store and retrieve knowledge values', () => {
17
+ store.update('transactions:list:budget-123', 1000);
18
+ expect(store.get('transactions:list:budget-123')).toBe(1000);
19
+ });
20
+
21
+ it('should update existing knowledge values', () => {
22
+ store.update('transactions:list:budget-123', 1000);
23
+ store.update('transactions:list:budget-123', 1005);
24
+ expect(store.get('transactions:list:budget-123')).toBe(1005);
25
+ });
26
+
27
+ it('should handle multiple cache keys independently', () => {
28
+ store.update('transactions:list:budget-123', 1000);
29
+ store.update('accounts:list:budget-123', 2000);
30
+ store.update('transactions:list:budget-456', 3000);
31
+
32
+ expect(store.get('transactions:list:budget-123')).toBe(1000);
33
+ expect(store.get('accounts:list:budget-123')).toBe(2000);
34
+ expect(store.get('transactions:list:budget-456')).toBe(3000);
35
+ });
36
+
37
+ it('should return correct stats with entryCount and entries object', () => {
38
+ store.update('key1', 100);
39
+ store.update('key2', 200);
40
+ store.update('key3', 300);
41
+
42
+ const stats = store.getStats();
43
+ expect(stats.entryCount).toBe(3);
44
+ expect(stats.entries).toEqual({
45
+ key1: 100,
46
+ key2: 200,
47
+ key3: 300,
48
+ });
49
+ });
50
+ });
51
+
52
+ describe('Validation', () => {
53
+ it('should throw error for negative server_knowledge values', () => {
54
+ expect(() => store.update('key', -1)).toThrow(
55
+ 'server_knowledge must be non-negative, got: -1',
56
+ );
57
+ });
58
+
59
+ it('should accept zero as valid server_knowledge', () => {
60
+ expect(() => store.update('key', 0)).not.toThrow();
61
+ expect(store.get('key')).toBe(0);
62
+ });
63
+
64
+ it('should accept large positive integers', () => {
65
+ const largeValue = 999999;
66
+ expect(() => store.update('key', largeValue)).not.toThrow();
67
+ expect(store.get('key')).toBe(largeValue);
68
+ });
69
+ });
70
+
71
+ describe('Reset Operations', () => {
72
+ beforeEach(() => {
73
+ store.update('transactions:list:budget-123:all:all', 1000);
74
+ store.update('accounts:list:budget-123', 2000);
75
+ store.update('transactions:list:budget-456', 3000);
76
+ store.update('categories:list:budget-123', 4000);
77
+ });
78
+
79
+ it('should reset all knowledge when called without pattern', () => {
80
+ store.reset();
81
+ expect(store.get('transactions:list:budget-123:all:all')).toBeUndefined();
82
+ expect(store.get('accounts:list:budget-123')).toBeUndefined();
83
+ expect(store.get('transactions:list:budget-456')).toBeUndefined();
84
+ expect(store.get('categories:list:budget-123')).toBeUndefined();
85
+ expect(store.getStats().entryCount).toBe(0);
86
+ });
87
+
88
+ it('should reset only matching keys when pattern provided', () => {
89
+ store.reset('transactions:list:budget-123');
90
+
91
+ // Should match and delete
92
+ expect(store.get('transactions:list:budget-123:all:all')).toBeUndefined();
93
+
94
+ // Should not match and preserve
95
+ expect(store.get('accounts:list:budget-123')).toBe(2000);
96
+ expect(store.get('transactions:list:budget-456')).toBe(3000);
97
+ expect(store.get('categories:list:budget-123')).toBe(4000);
98
+ });
99
+
100
+ it('should not reset non-matching keys', () => {
101
+ store.reset('non-existent-pattern');
102
+
103
+ // All keys should remain
104
+ expect(store.get('transactions:list:budget-123:all:all')).toBe(1000);
105
+ expect(store.get('accounts:list:budget-123')).toBe(2000);
106
+ expect(store.get('transactions:list:budget-456')).toBe(3000);
107
+ expect(store.get('categories:list:budget-123')).toBe(4000);
108
+ });
109
+
110
+ it('should handle resetByBudgetId correctly', () => {
111
+ store.resetByBudgetId('budget-123');
112
+
113
+ // Should match all keys containing ':budget-123'
114
+ expect(store.get('transactions:list:budget-123:all:all')).toBeUndefined();
115
+ expect(store.get('accounts:list:budget-123')).toBeUndefined();
116
+ expect(store.get('categories:list:budget-123')).toBeUndefined();
117
+
118
+ // Should not match budget-456
119
+ expect(store.get('transactions:list:budget-456')).toBe(3000);
120
+ });
121
+
122
+ it('should not affect other budgets when resetting by budget ID', () => {
123
+ store.update('transactions:list:budget-789', 5000);
124
+
125
+ store.resetByBudgetId('budget-123');
126
+
127
+ // budget-456 and budget-789 should remain
128
+ expect(store.get('transactions:list:budget-456')).toBe(3000);
129
+ expect(store.get('transactions:list:budget-789')).toBe(5000);
130
+ });
131
+
132
+ it('should handle empty store gracefully', () => {
133
+ const emptyStore = new ServerKnowledgeStore();
134
+ expect(() => emptyStore.reset()).not.toThrow();
135
+ expect(() => emptyStore.reset('pattern')).not.toThrow();
136
+ expect(() => emptyStore.resetByBudgetId('budget-123')).not.toThrow();
137
+ });
138
+ });
139
+
140
+ describe('Edge Cases', () => {
141
+ it('should handle very long cache keys', () => {
142
+ const longKey = 'a'.repeat(250);
143
+ store.update(longKey, 1000);
144
+ expect(store.get(longKey)).toBe(1000);
145
+ });
146
+
147
+ it('should handle special characters in cache keys', () => {
148
+ const specialKey = 'transactions:list:budget-123:date:2024-01-01_2024-12-31';
149
+ store.update(specialKey, 1000);
150
+ expect(store.get(specialKey)).toBe(1000);
151
+ });
152
+
153
+ it('should handle rapid updates to same key', () => {
154
+ store.update('key', 100);
155
+ store.update('key', 200);
156
+ store.update('key', 300);
157
+ store.update('key', 400);
158
+
159
+ // Last write wins
160
+ expect(store.get('key')).toBe(400);
161
+ });
162
+
163
+ it('should maintain isolation between different store instances', () => {
164
+ const store1 = new ServerKnowledgeStore();
165
+ const store2 = new ServerKnowledgeStore();
166
+
167
+ store1.update('key', 100);
168
+ store2.update('key', 200);
169
+
170
+ expect(store1.get('key')).toBe(100);
171
+ expect(store2.get('key')).toBe(200);
172
+ });
173
+ });
174
+ });