@dizzlkheinz/ynab-mcpb 0.18.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CLAUDE.md +87 -8
  3. package/bin/ynab-mcp-server.cjs +2 -2
  4. package/bin/ynab-mcp-server.js +3 -3
  5. package/biome.json +39 -0
  6. package/dist/bundle/index.cjs +67 -67
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +27 -27
  9. package/dist/server/YNABMCPServer.d.ts +3 -4
  10. package/dist/server/YNABMCPServer.js +111 -116
  11. package/dist/server/budgetResolver.d.ts +6 -5
  12. package/dist/server/budgetResolver.js +46 -36
  13. package/dist/server/cacheKeys.js +6 -6
  14. package/dist/server/cacheManager.js +14 -11
  15. package/dist/server/completions.d.ts +2 -2
  16. package/dist/server/completions.js +20 -15
  17. package/dist/server/config.d.ts +10 -5
  18. package/dist/server/config.js +24 -7
  19. package/dist/server/deltaCache.d.ts +2 -2
  20. package/dist/server/deltaCache.js +22 -16
  21. package/dist/server/deltaCache.merge.d.ts +2 -2
  22. package/dist/server/diagnostics.d.ts +4 -4
  23. package/dist/server/diagnostics.js +38 -32
  24. package/dist/server/errorHandler.d.ts +5 -12
  25. package/dist/server/errorHandler.js +219 -217
  26. package/dist/server/prompts.d.ts +2 -2
  27. package/dist/server/prompts.js +45 -45
  28. package/dist/server/rateLimiter.js +4 -4
  29. package/dist/server/requestLogger.d.ts +1 -1
  30. package/dist/server/requestLogger.js +40 -35
  31. package/dist/server/resources.d.ts +3 -3
  32. package/dist/server/resources.js +55 -52
  33. package/dist/server/responseFormatter.js +6 -6
  34. package/dist/server/securityMiddleware.d.ts +2 -2
  35. package/dist/server/securityMiddleware.js +22 -20
  36. package/dist/server/serverKnowledgeStore.js +1 -1
  37. package/dist/server/toolRegistry.d.ts +3 -3
  38. package/dist/server/toolRegistry.js +47 -40
  39. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  40. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  41. package/dist/tools/accountTools.d.ts +9 -8
  42. package/dist/tools/accountTools.js +47 -47
  43. package/dist/tools/adapters.d.ts +13 -8
  44. package/dist/tools/adapters.js +21 -11
  45. package/dist/tools/budgetTools.d.ts +8 -7
  46. package/dist/tools/budgetTools.js +22 -22
  47. package/dist/tools/categoryTools.d.ts +9 -8
  48. package/dist/tools/categoryTools.js +68 -59
  49. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  50. package/dist/tools/compareTransactions/formatter.js +9 -9
  51. package/dist/tools/compareTransactions/index.d.ts +6 -6
  52. package/dist/tools/compareTransactions/index.js +58 -43
  53. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  54. package/dist/tools/compareTransactions/matcher.js +28 -15
  55. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  56. package/dist/tools/compareTransactions/parser.js +144 -138
  57. package/dist/tools/compareTransactions/types.d.ts +4 -4
  58. package/dist/tools/compareTransactions.d.ts +1 -1
  59. package/dist/tools/compareTransactions.js +1 -1
  60. package/dist/tools/deltaFetcher.d.ts +2 -2
  61. package/dist/tools/deltaFetcher.js +16 -15
  62. package/dist/tools/deltaSupport.d.ts +4 -4
  63. package/dist/tools/deltaSupport.js +35 -41
  64. package/dist/tools/exportTransactions.d.ts +5 -4
  65. package/dist/tools/exportTransactions.js +61 -59
  66. package/dist/tools/monthTools.d.ts +7 -6
  67. package/dist/tools/monthTools.js +31 -29
  68. package/dist/tools/payeeTools.d.ts +7 -6
  69. package/dist/tools/payeeTools.js +28 -28
  70. package/dist/tools/reconcileAdapter.d.ts +2 -2
  71. package/dist/tools/reconcileAdapter.js +21 -11
  72. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  73. package/dist/tools/reconciliation/analyzer.js +136 -57
  74. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  75. package/dist/tools/reconciliation/csvParser.js +128 -104
  76. package/dist/tools/reconciliation/executor.d.ts +4 -4
  77. package/dist/tools/reconciliation/executor.js +148 -109
  78. package/dist/tools/reconciliation/index.d.ts +10 -10
  79. package/dist/tools/reconciliation/index.js +96 -83
  80. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  81. package/dist/tools/reconciliation/matcher.js +17 -16
  82. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  83. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  84. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  85. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  86. package/dist/tools/reconciliation/reportFormatter.js +79 -54
  87. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  88. package/dist/tools/reconciliation/types.d.ts +19 -16
  89. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  90. package/dist/tools/schemas/common.d.ts +1 -1
  91. package/dist/tools/schemas/common.js +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  93. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  94. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  95. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  96. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  97. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  98. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  99. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  100. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  101. package/dist/tools/schemas/outputs/index.js +14 -14
  102. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  103. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  104. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  105. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  107. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  109. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  110. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  111. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  112. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  113. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  114. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  115. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  116. package/dist/tools/transactionReadTools.d.ts +11 -0
  117. package/dist/tools/transactionReadTools.js +202 -0
  118. package/dist/tools/transactionSchemas.d.ts +309 -0
  119. package/dist/tools/transactionSchemas.js +235 -0
  120. package/dist/tools/transactionTools.d.ts +6 -302
  121. package/dist/tools/transactionTools.js +7 -2054
  122. package/dist/tools/transactionUtils.d.ts +31 -0
  123. package/dist/tools/transactionUtils.js +364 -0
  124. package/dist/tools/transactionWriteTools.d.ts +20 -0
  125. package/dist/tools/transactionWriteTools.js +1342 -0
  126. package/dist/tools/utilityTools.d.ts +5 -4
  127. package/dist/tools/utilityTools.js +11 -11
  128. package/dist/types/index.d.ts +7 -7
  129. package/dist/types/index.js +6 -6
  130. package/dist/types/reconciliation.d.ts +1 -1
  131. package/dist/types/toolRegistration.d.ts +14 -12
  132. package/dist/utils/amountUtils.js +1 -1
  133. package/dist/utils/dateUtils.js +4 -4
  134. package/dist/utils/errors.d.ts +3 -3
  135. package/dist/utils/errors.js +4 -4
  136. package/dist/utils/money.d.ts +2 -2
  137. package/dist/utils/money.js +8 -8
  138. package/dist/utils/validationError.d.ts +1 -1
  139. package/dist/utils/validationError.js +1 -1
  140. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  141. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  142. package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
  143. package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
  144. package/esbuild.config.mjs +53 -50
  145. package/meta.json +12548 -12548
  146. package/package.json +98 -109
  147. package/scripts/analyze-bundle.mjs +33 -30
  148. package/scripts/create-pr-description.js +169 -120
  149. package/scripts/run-all-tests.js +205 -0
  150. package/scripts/run-domain-integration-tests.js +28 -18
  151. package/scripts/run-generate-mcpb.js +19 -17
  152. package/scripts/run-throttled-integration-tests.js +92 -83
  153. package/scripts/test-delta-params.mjs +149 -120
  154. package/scripts/test-recommendations.ts +36 -32
  155. package/scripts/tmpTransaction.ts +80 -43
  156. package/scripts/validate-env.js +98 -91
  157. package/scripts/verify-build.js +78 -76
  158. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  159. package/src/__tests__/performance.test.ts +723 -671
  160. package/src/__tests__/setup.ts +442 -395
  161. package/src/__tests__/smoke.e2e.test.ts +41 -39
  162. package/src/__tests__/testRunner.ts +314 -295
  163. package/src/__tests__/testUtils.ts +456 -364
  164. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  165. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  166. package/src/index.ts +68 -59
  167. package/src/server/CLAUDE.md +480 -0
  168. package/src/server/YNABMCPServer.ts +821 -794
  169. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  170. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  171. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  172. package/src/server/__tests__/cacheManager.test.ts +891 -874
  173. package/src/server/__tests__/completions.integration.test.ts +115 -106
  174. package/src/server/__tests__/completions.test.ts +334 -313
  175. package/src/server/__tests__/config.test.ts +98 -86
  176. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  177. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  178. package/src/server/__tests__/deltaCache.test.ts +946 -759
  179. package/src/server/__tests__/diagnostics.test.ts +825 -792
  180. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  181. package/src/server/__tests__/errorHandler.test.ts +402 -397
  182. package/src/server/__tests__/prompts.test.ts +424 -347
  183. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  184. package/src/server/__tests__/requestLogger.test.ts +443 -403
  185. package/src/server/__tests__/resources.template.test.ts +196 -185
  186. package/src/server/__tests__/resources.test.ts +294 -288
  187. package/src/server/__tests__/security.integration.test.ts +487 -421
  188. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  189. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  190. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  191. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  192. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  193. package/src/server/budgetResolver.ts +221 -181
  194. package/src/server/cacheKeys.ts +6 -6
  195. package/src/server/cacheManager.ts +498 -484
  196. package/src/server/completions.ts +267 -243
  197. package/src/server/config.ts +35 -14
  198. package/src/server/deltaCache.merge.ts +146 -128
  199. package/src/server/deltaCache.ts +352 -309
  200. package/src/server/diagnostics.ts +257 -242
  201. package/src/server/errorHandler.ts +747 -744
  202. package/src/server/prompts.ts +181 -176
  203. package/src/server/rateLimiter.ts +131 -129
  204. package/src/server/requestLogger.ts +350 -322
  205. package/src/server/resources.ts +442 -374
  206. package/src/server/responseFormatter.ts +41 -37
  207. package/src/server/securityMiddleware.ts +223 -205
  208. package/src/server/serverKnowledgeStore.ts +67 -67
  209. package/src/server/toolRegistry.ts +508 -474
  210. package/src/tools/CLAUDE.md +604 -0
  211. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  212. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  213. package/src/tools/__tests__/accountTools.test.ts +685 -638
  214. package/src/tools/__tests__/adapters.test.ts +142 -108
  215. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  216. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  217. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  218. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  219. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  220. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  221. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  222. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  223. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  224. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  225. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  226. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  227. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  228. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  229. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  230. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  231. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  232. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  234. package/src/tools/__tests__/monthTools.test.ts +561 -512
  235. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  236. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  237. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  238. package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
  239. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  240. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  241. package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
  242. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  243. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  244. package/src/tools/accountTools.ts +293 -271
  245. package/src/tools/adapters.ts +120 -63
  246. package/src/tools/budgetTools.ts +121 -116
  247. package/src/tools/categoryTools.ts +379 -339
  248. package/src/tools/compareTransactions/formatter.ts +131 -119
  249. package/src/tools/compareTransactions/index.ts +249 -214
  250. package/src/tools/compareTransactions/matcher.ts +259 -209
  251. package/src/tools/compareTransactions/parser.ts +517 -487
  252. package/src/tools/compareTransactions/types.ts +38 -38
  253. package/src/tools/compareTransactions.ts +1 -1
  254. package/src/tools/deltaFetcher.ts +281 -260
  255. package/src/tools/deltaSupport.ts +264 -259
  256. package/src/tools/exportTransactions.ts +230 -218
  257. package/src/tools/monthTools.ts +180 -165
  258. package/src/tools/payeeTools.ts +152 -140
  259. package/src/tools/reconcileAdapter.ts +297 -246
  260. package/src/tools/reconciliation/CLAUDE.md +506 -0
  261. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
  262. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
  263. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
  264. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  265. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  266. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  267. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  268. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  269. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  270. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  271. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
  272. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  273. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
  274. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
  275. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
  276. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  277. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
  278. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  279. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  280. package/src/tools/reconciliation/analyzer.ts +582 -406
  281. package/src/tools/reconciliation/csvParser.ts +656 -609
  282. package/src/tools/reconciliation/executor.ts +1290 -1128
  283. package/src/tools/reconciliation/index.ts +580 -528
  284. package/src/tools/reconciliation/matcher.ts +256 -240
  285. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  286. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  287. package/src/tools/reconciliation/reportFormatter.ts +349 -276
  288. package/src/tools/reconciliation/signDetector.ts +89 -83
  289. package/src/tools/reconciliation/types.ts +164 -153
  290. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  291. package/src/tools/schemas/CLAUDE.md +546 -0
  292. package/src/tools/schemas/common.ts +1 -1
  293. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  294. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  295. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  296. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  297. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  298. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  299. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  300. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  301. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  302. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  303. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  304. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  305. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  306. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  307. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  308. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  309. package/src/tools/schemas/outputs/index.ts +163 -163
  310. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  311. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  312. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  313. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  314. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  315. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  316. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  317. package/src/tools/toolCategories.ts +114 -114
  318. package/src/tools/transactionReadTools.ts +327 -0
  319. package/src/tools/transactionSchemas.ts +484 -0
  320. package/src/tools/transactionTools.ts +107 -2990
  321. package/src/tools/transactionUtils.ts +621 -0
  322. package/src/tools/transactionWriteTools.ts +2110 -0
  323. package/src/tools/utilityTools.ts +46 -41
  324. package/src/types/CLAUDE.md +477 -0
  325. package/src/types/__tests__/index.test.ts +51 -51
  326. package/src/types/index.ts +43 -39
  327. package/src/types/integration-tests.d.ts +26 -26
  328. package/src/types/reconciliation.ts +29 -29
  329. package/src/types/toolAnnotations.ts +30 -30
  330. package/src/types/toolRegistration.ts +43 -32
  331. package/src/utils/CLAUDE.md +508 -0
  332. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  333. package/src/utils/__tests__/money.test.ts +193 -187
  334. package/src/utils/amountUtils.ts +5 -5
  335. package/src/utils/baseError.ts +5 -5
  336. package/src/utils/dateUtils.ts +29 -26
  337. package/src/utils/errors.ts +14 -14
  338. package/src/utils/money.ts +66 -52
  339. package/src/utils/validationError.ts +1 -1
  340. package/tsconfig.json +29 -29
  341. package/tsconfig.prod.json +16 -16
  342. package/vitest-reporters/split-json-reporter.ts +247 -204
  343. package/vitest.config.ts +99 -95
  344. package/.prettierignore +0 -10
  345. package/.prettierrc.json +0 -10
  346. package/eslint.config.js +0 -49
@@ -1,9 +1,9 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  function parseBool(value, fallback) {
3
3
  if (value === undefined)
4
4
  return fallback;
5
5
  const v = value.trim().toLowerCase();
6
- return v === '1' || v === 'true' || v === 'yes' || v === 'on';
6
+ return v === "1" || v === "true" || v === "yes" || v === "on";
7
7
  }
8
8
  function parseIntSafe(value, fallback) {
9
9
  if (value === undefined)
@@ -14,15 +14,15 @@ function parseIntSafe(value, fallback) {
14
14
  class ResponseFormatter {
15
15
  constructor() {
16
16
  this.als = new AsyncLocalStorage();
17
- this.defaultMinify = parseBool(process.env['YNAB_MCP_MINIFY_OUTPUT'], true);
18
- this.prettySpaces = parseIntSafe(process.env['YNAB_MCP_PRETTY_SPACES'], 2);
17
+ this.defaultMinify = parseBool(process.env["YNAB_MCP_MINIFY_OUTPUT"], true);
18
+ this.prettySpaces = parseIntSafe(process.env["YNAB_MCP_PRETTY_SPACES"], 2);
19
19
  }
20
20
  configure(options) {
21
21
  if (!options)
22
22
  return;
23
- if (typeof options.defaultMinify === 'boolean')
23
+ if (typeof options.defaultMinify === "boolean")
24
24
  this.defaultMinify = options.defaultMinify;
25
- if (typeof options.prettySpaces === 'number' && options.prettySpaces >= 0) {
25
+ if (typeof options.prettySpaces === "number" && options.prettySpaces >= 0) {
26
26
  this.prettySpaces = options.prettySpaces;
27
27
  }
28
28
  }
@@ -1,5 +1,5 @@
1
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
- import { z } from 'zod/v4';
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { z } from "zod/v4";
3
3
  export interface SecurityContext {
4
4
  accessToken: string;
5
5
  toolName: string;
@@ -1,19 +1,19 @@
1
- import { z } from 'zod/v4';
2
- import { fromZodError } from 'zod-validation-error';
3
- import { globalRateLimiter, RateLimitError } from './rateLimiter.js';
4
- import { globalRequestLogger } from './requestLogger.js';
5
- import { ErrorHandler } from './errorHandler.js';
6
- import { responseFormatter } from './responseFormatter.js';
1
+ import { fromZodError } from "zod-validation-error";
2
+ import { z } from "zod/v4";
3
+ import { createErrorHandler } from "./errorHandler.js";
4
+ import { RateLimitError, globalRateLimiter } from "./rateLimiter.js";
5
+ import { globalRequestLogger } from "./requestLogger.js";
6
+ import { responseFormatter } from "./responseFormatter.js";
7
7
  export class SecurityMiddleware {
8
8
  static async withSecurity(context, schema, operation) {
9
9
  const startTime = Date.now();
10
10
  try {
11
- const validatedParams = await this.validateInput(schema, context.parameters);
12
- await this.checkRateLimit(context.accessToken);
13
- globalRateLimiter.recordRequest(this.hashToken(context.accessToken));
11
+ const validatedParams = await SecurityMiddleware.validateInput(schema, context.parameters);
12
+ await SecurityMiddleware.checkRateLimit(context.accessToken);
13
+ globalRateLimiter.recordRequest(SecurityMiddleware.hashToken(context.accessToken));
14
14
  const result = await operation(validatedParams);
15
15
  const duration = Date.now() - startTime;
16
- const rateLimitInfo = globalRateLimiter.getStatus(this.hashToken(context.accessToken));
16
+ const rateLimitInfo = globalRateLimiter.getStatus(SecurityMiddleware.hashToken(context.accessToken));
17
17
  globalRequestLogger.logSuccess(context.toolName, context.operation, context.parameters, duration, {
18
18
  remaining: rateLimitInfo.remaining,
19
19
  isLimited: rateLimitInfo.isLimited,
@@ -22,17 +22,19 @@ export class SecurityMiddleware {
22
22
  }
23
23
  catch (error) {
24
24
  const duration = Date.now() - startTime;
25
- const rateLimitInfo = globalRateLimiter.getStatus(this.hashToken(context.accessToken));
26
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
25
+ const rateLimitInfo = globalRateLimiter.getStatus(SecurityMiddleware.hashToken(context.accessToken));
26
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
27
27
  globalRequestLogger.logError(context.toolName, context.operation, context.parameters, errorMessage, duration, {
28
28
  remaining: rateLimitInfo.remaining,
29
29
  isLimited: rateLimitInfo.isLimited,
30
30
  });
31
31
  if (error instanceof RateLimitError) {
32
- return this.createRateLimitErrorResponse(error);
32
+ return SecurityMiddleware.createRateLimitErrorResponse(error);
33
33
  }
34
- if (error instanceof Error && error.message.includes('Validation failed')) {
35
- return ErrorHandler.createValidationError('Invalid parameters for ' + context.toolName, error.message);
34
+ if (error instanceof Error &&
35
+ error.message.includes("Validation failed")) {
36
+ const errorHandler = createErrorHandler(responseFormatter);
37
+ return errorHandler.createValidationError(`Invalid parameters for ${context.toolName}`, error.message);
36
38
  }
37
39
  throw error;
38
40
  }
@@ -50,10 +52,10 @@ export class SecurityMiddleware {
50
52
  }
51
53
  }
52
54
  static async checkRateLimit(accessToken) {
53
- const tokenHash = this.hashToken(accessToken);
55
+ const tokenHash = SecurityMiddleware.hashToken(accessToken);
54
56
  const rateLimitInfo = globalRateLimiter.isAllowed(tokenHash);
55
57
  if (rateLimitInfo.isLimited) {
56
- throw new RateLimitError('Rate limit exceeded. Please wait before making additional requests.', rateLimitInfo.resetTime, rateLimitInfo.remaining);
58
+ throw new RateLimitError("Rate limit exceeded. Please wait before making additional requests.", rateLimitInfo.resetTime, rateLimitInfo.remaining);
57
59
  }
58
60
  }
59
61
  static createRateLimitErrorResponse(error) {
@@ -61,10 +63,10 @@ export class SecurityMiddleware {
61
63
  isError: true,
62
64
  content: [
63
65
  {
64
- type: 'text',
66
+ type: "text",
65
67
  text: responseFormatter.format({
66
68
  error: {
67
- code: 'RATE_LIMIT_EXCEEDED',
69
+ code: "RATE_LIMIT_EXCEEDED",
68
70
  message: error.message,
69
71
  details: {
70
72
  resetTime: error.resetTime.toISOString(),
@@ -88,7 +90,7 @@ export class SecurityMiddleware {
88
90
  static getSecurityStats() {
89
91
  return {
90
92
  rateLimitStats: {
91
- message: 'Rate limiting is active with YNAB API limits (200 requests/hour)',
93
+ message: "Rate limiting is active with YNAB API limits (200 requests/hour)",
92
94
  },
93
95
  requestStats: globalRequestLogger.getStats(),
94
96
  };
@@ -12,7 +12,7 @@ export class ServerKnowledgeStore {
12
12
  this.knowledge.set(cacheKey, value);
13
13
  }
14
14
  reset(keyPattern) {
15
- if (keyPattern === undefined || keyPattern === '') {
15
+ if (keyPattern === undefined || keyPattern === "") {
16
16
  this.knowledge.clear();
17
17
  return;
18
18
  }
@@ -1,6 +1,6 @@
1
- import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { z } from 'zod/v4';
3
- import type { MCPToolAnnotations } from '../types/toolAnnotations.js';
1
+ import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { z } from "zod/v4";
3
+ import type { MCPToolAnnotations } from "../types/toolAnnotations.js";
4
4
  export type SecurityWrapperFactory = <T extends Record<string, unknown>>(namespace: string, operation: string, schema: z.ZodSchema<T>) => (accessToken: string) => (params: Record<string, unknown>) => (handler: (validated: T) => Promise<CallToolResult>) => Promise<CallToolResult>;
5
5
  export interface ErrorHandlerContract {
6
6
  handleError(error: unknown, context: string): CallToolResult;
@@ -1,13 +1,13 @@
1
- import { z, toJSONSchema } from 'zod/v4';
2
- import { fromZodError } from 'zod-validation-error';
1
+ import { fromZodError } from "zod-validation-error";
2
+ import { toJSONSchema, z } from "zod/v4";
3
3
  export class DefaultArgumentResolutionError extends Error {
4
4
  constructor(result) {
5
- super('Default argument resolution failed');
5
+ super("Default argument resolution failed");
6
6
  this.result = result;
7
- this.name = 'DefaultArgumentResolutionError';
7
+ this.name = "DefaultArgumentResolutionError";
8
8
  }
9
9
  }
10
- const MINIFY_HINT_KEYS = ['minify', '_minify', '__minify'];
10
+ const MINIFY_HINT_KEYS = ["minify", "_minify", "__minify"];
11
11
  export class ToolRegistry {
12
12
  constructor(deps) {
13
13
  this.deps = deps;
@@ -22,7 +22,7 @@ export class ToolRegistry {
22
22
  const resolved = {
23
23
  ...definition,
24
24
  security: {
25
- namespace: definition.security?.namespace ?? 'ynab',
25
+ namespace: definition.security?.namespace ?? "ynab",
26
26
  operation: definition.security?.operation ?? definition.name,
27
27
  },
28
28
  };
@@ -42,7 +42,7 @@ export class ToolRegistry {
42
42
  inputSchema,
43
43
  };
44
44
  if (tool.outputSchema) {
45
- const outputSchema = this.generateJsonSchema(tool.outputSchema, 'output');
45
+ const outputSchema = this.generateJsonSchema(tool.outputSchema, "output");
46
46
  result.outputSchema = outputSchema;
47
47
  }
48
48
  if (tool.metadata?.annotations) {
@@ -78,7 +78,7 @@ export class ToolRegistry {
78
78
  async executeTool(options) {
79
79
  const tool = this.tools.get(options.name);
80
80
  if (!tool) {
81
- return this.deps.errorHandler.createValidationError(`Unknown tool: ${options.name}`, 'The requested tool is not registered with the server');
81
+ return this.deps.errorHandler.createValidationError(`Unknown tool: ${options.name}`, "The requested tool is not registered with the server");
82
82
  }
83
83
  if (this.deps.validateAccessToken) {
84
84
  try {
@@ -107,9 +107,9 @@ export class ToolRegistry {
107
107
  if (this.isCallToolResult(error)) {
108
108
  return error;
109
109
  }
110
- return this.deps.errorHandler.createValidationError('Invalid parameters', error instanceof Error
110
+ return this.deps.errorHandler.createValidationError("Invalid parameters", error instanceof Error
111
111
  ? error.message
112
- : 'Unknown error during default argument resolution');
112
+ : "Unknown error during default argument resolution");
113
113
  }
114
114
  }
115
115
  const rawArguments = {
@@ -160,9 +160,9 @@ export class ToolRegistry {
160
160
  }
161
161
  }
162
162
  isCallToolResult(value) {
163
- return (typeof value === 'object' &&
163
+ return (typeof value === "object" &&
164
164
  value !== null &&
165
- 'content' in value &&
165
+ "content" in value &&
166
166
  Array.isArray(value.content));
167
167
  }
168
168
  normalizeSecurityError(error, tool) {
@@ -170,18 +170,18 @@ export class ToolRegistry {
170
170
  const validationError = fromZodError(error);
171
171
  return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, validationError.message);
172
172
  }
173
- if (error instanceof Error && error.message.includes('Validation failed')) {
173
+ if (error instanceof Error && error.message.includes("Validation failed")) {
174
174
  return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, error.message);
175
175
  }
176
176
  return this.deps.errorHandler.handleError(error, `executing ${tool.name}`);
177
177
  }
178
178
  extractMinifyOverride(options, args) {
179
- if (typeof options.minifyOverride === 'boolean') {
179
+ if (typeof options.minifyOverride === "boolean") {
180
180
  return options.minifyOverride;
181
181
  }
182
182
  for (const key of MINIFY_HINT_KEYS) {
183
183
  const value = args[key];
184
- if (typeof value === 'boolean') {
184
+ if (typeof value === "boolean") {
185
185
  delete args[key];
186
186
  return value;
187
187
  }
@@ -189,40 +189,41 @@ export class ToolRegistry {
189
189
  return undefined;
190
190
  }
191
191
  assertValidDefinition(definition) {
192
- if (!definition || typeof definition !== 'object') {
193
- throw new Error('Tool definition must be an object');
192
+ if (!definition || typeof definition !== "object") {
193
+ throw new Error("Tool definition must be an object");
194
194
  }
195
- if (!definition.name || typeof definition.name !== 'string') {
196
- throw new Error('Tool definition requires a non-empty name');
195
+ if (!definition.name || typeof definition.name !== "string") {
196
+ throw new Error("Tool definition requires a non-empty name");
197
197
  }
198
198
  if (!ToolRegistry.MCP_TOOL_NAME_REGEX.test(definition.name)) {
199
- throw new Error(`Tool name '${definition.name}' violates MCP guidelines: ` +
200
- `must be 1-128 chars using only [a-zA-Z0-9_.-]`);
199
+ throw new Error(`Tool name '${definition.name}' violates MCP guidelines: must be 1-128 chars using only [a-zA-Z0-9_.-]`);
201
200
  }
202
- if (!definition.description || typeof definition.description !== 'string') {
201
+ if (!definition.description || typeof definition.description !== "string") {
203
202
  throw new Error(`Tool '${definition.name}' requires a description`);
204
203
  }
205
- if (!definition.inputSchema || typeof definition.inputSchema.parse !== 'function') {
204
+ if (!definition.inputSchema ||
205
+ typeof definition.inputSchema.parse !== "function") {
206
206
  throw new Error(`Tool '${definition.name}' requires a valid Zod schema`);
207
207
  }
208
- if (definition.outputSchema && typeof definition.outputSchema.parse !== 'function') {
208
+ if (definition.outputSchema &&
209
+ typeof definition.outputSchema.parse !== "function") {
209
210
  throw new Error(`Tool '${definition.name}' outputSchema must be a valid Zod schema when provided`);
210
211
  }
211
- if (typeof definition.handler !== 'function') {
212
+ if (typeof definition.handler !== "function") {
212
213
  throw new Error(`Tool '${definition.name}' requires a handler function`);
213
214
  }
214
215
  if (definition.defaultArgumentResolver &&
215
- typeof definition.defaultArgumentResolver !== 'function') {
216
+ typeof definition.defaultArgumentResolver !== "function") {
216
217
  throw new Error(`Tool '${definition.name}' defaultArgumentResolver must be a function when provided`);
217
218
  }
218
219
  }
219
- generateJsonSchema(schema, ioMode = 'input') {
220
+ generateJsonSchema(schema, ioMode = "input") {
220
221
  try {
221
- return toJSONSchema(schema, { target: 'draft-2020-12', io: ioMode });
222
+ return toJSONSchema(schema, { target: "draft-2020-12", io: ioMode });
222
223
  }
223
224
  catch (error) {
224
225
  console.warn(`Failed to generate JSON schema for tool: ${error}`);
225
- return { type: 'object', additionalProperties: true };
226
+ return { type: "object", additionalProperties: true };
226
227
  }
227
228
  }
228
229
  validateOutput(toolName, output) {
@@ -231,18 +232,21 @@ export class ToolRegistry {
231
232
  return output;
232
233
  }
233
234
  if (!output.content || output.content.length === 0) {
234
- return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, 'Handler returned empty content', ['Ensure the handler returns valid content in the response']);
235
+ return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, "Handler returned empty content", ["Ensure the handler returns valid content in the response"]);
235
236
  }
236
237
  const invalidItems = [];
237
238
  for (let i = 0; i < output.content.length; i++) {
238
239
  const item = output.content[i];
239
240
  if (!item) {
240
- invalidItems.push({ index: i, reason: 'item is null or undefined' });
241
+ invalidItems.push({ index: i, reason: "item is null or undefined" });
241
242
  }
242
- else if (item.type !== 'text') {
243
- invalidItems.push({ index: i, reason: `type is "${item.type}" instead of "text"` });
243
+ else if (item.type !== "text") {
244
+ invalidItems.push({
245
+ index: i,
246
+ reason: `type is "${item.type}" instead of "text"`,
247
+ });
244
248
  }
245
- else if (typeof item.text !== 'string') {
249
+ else if (typeof item.text !== "string") {
246
250
  invalidItems.push({
247
251
  index: i,
248
252
  reason: `text property is ${typeof item.text} instead of string`,
@@ -252,27 +256,30 @@ export class ToolRegistry {
252
256
  if (invalidItems.length > 0) {
253
257
  const invalidItemsDetails = invalidItems
254
258
  .map((inv) => ` - Item ${inv.index}: ${inv.reason}`)
255
- .join('\n');
259
+ .join("\n");
256
260
  return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Handler returned invalid content items (${invalidItems.length} of ${output.content.length} failed):\n${invalidItemsDetails}`, ['Ensure all content items have type="text" and a valid text property']);
257
261
  }
258
262
  const firstContent = output.content[0];
259
- if (firstContent.type !== 'text') {
260
- throw new Error('Unexpected: firstContent is not text after validation');
263
+ if (!firstContent) {
264
+ return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, "Handler returned empty content", ["Ensure the handler returns valid content in the response"]);
265
+ }
266
+ if (firstContent.type !== "text") {
267
+ throw new Error("Unexpected: firstContent is not text after validation");
261
268
  }
262
269
  let parsedOutput;
263
270
  try {
264
271
  parsedOutput = JSON.parse(firstContent.text);
265
272
  }
266
273
  catch (parseError) {
267
- return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Invalid JSON in handler output: ${parseError instanceof Error ? parseError.message : String(parseError)}`, ['Ensure the handler returns valid JSON']);
274
+ return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Invalid JSON in handler output: ${parseError instanceof Error ? parseError.message : String(parseError)}`, ["Ensure the handler returns valid JSON"]);
268
275
  }
269
276
  const result = validator.safeParse(parsedOutput);
270
277
  if (!result.success) {
271
278
  const validationError = fromZodError(result.error);
272
279
  const validationErrors = validationError.message;
273
280
  return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Handler output does not match declared output schema: ${validationErrors}`, [
274
- 'Check that the handler returns data matching the output schema',
275
- 'Review the tool definition output schema',
281
+ "Check that the handler returns data matching the output schema",
282
+ "Review the tool definition output schema",
276
283
  ]);
277
284
  }
278
285
  return output;
@@ -1,11 +1,11 @@
1
- import { vi } from 'vitest';
2
- import { DeltaFetcher } from '../deltaFetcher.js';
1
+ import { vi } from "vitest";
2
+ import { DeltaFetcher } from "../deltaFetcher.js";
3
3
  export interface MockDeltaResult<T> {
4
4
  data: T[];
5
5
  wasCached?: boolean;
6
6
  usedDelta?: boolean;
7
7
  }
8
- type DeltaFetcherMethod = 'fetchAccounts' | 'fetchBudgets' | 'fetchCategories' | 'fetchMonths';
8
+ type DeltaFetcherMethod = "fetchAccounts" | "fetchBudgets" | "fetchCategories" | "fetchMonths";
9
9
  export declare function createDeltaFetcherMock<T>(method: DeltaFetcherMethod, result: MockDeltaResult<T>): {
10
10
  fetcher: DeltaFetcher;
11
11
  spy: ReturnType<typeof vi.fn>;
@@ -1,5 +1,5 @@
1
- import { vi } from 'vitest';
2
- import { DeltaFetcher } from '../deltaFetcher.js';
1
+ import { vi } from "vitest";
2
+ import { DeltaFetcher } from "../deltaFetcher.js";
3
3
  export function createDeltaFetcherMock(method, result) {
4
4
  const resolved = {
5
5
  wasCached: false,
@@ -1,10 +1,11 @@
1
- import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
- import * as ynab from 'ynab';
3
- import { z } from 'zod/v4';
4
- import type { DeltaFetcher } from './deltaFetcher.js';
5
- import type { DeltaCache } from '../server/deltaCache.js';
6
- import type { ServerKnowledgeStore } from '../server/serverKnowledgeStore.js';
7
- import type { ToolFactory } from '../types/toolRegistration.js';
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type * as ynab from "ynab";
3
+ import { z } from "zod/v4";
4
+ import type { DeltaCache } from "../server/deltaCache.js";
5
+ import type { ErrorHandler } from "../server/errorHandler.js";
6
+ import type { ServerKnowledgeStore } from "../server/serverKnowledgeStore.js";
7
+ import type { ToolFactory } from "../types/toolRegistration.js";
8
+ import type { DeltaFetcher } from "./deltaFetcher.js";
8
9
  export declare const ListAccountsSchema: z.ZodObject<{
9
10
  budget_id: z.ZodString;
10
11
  limit: z.ZodOptional<z.ZodNumber>;
@@ -33,7 +34,7 @@ export declare const CreateAccountSchema: z.ZodObject<{
33
34
  export type CreateAccountParams = z.infer<typeof CreateAccountSchema>;
34
35
  export declare function handleListAccounts(ynabAPI: ynab.API, deltaFetcher: DeltaFetcher, params: ListAccountsParams): Promise<CallToolResult>;
35
36
  export declare function handleListAccounts(ynabAPI: ynab.API, params: ListAccountsParams): Promise<CallToolResult>;
36
- export declare function handleGetAccount(ynabAPI: ynab.API, params: GetAccountParams): Promise<CallToolResult>;
37
+ export declare function handleGetAccount(ynabAPI: ynab.API, params: GetAccountParams, errorHandler?: ErrorHandler): Promise<CallToolResult>;
37
38
  export declare function handleCreateAccount(ynabAPI: ynab.API, deltaCache: DeltaCache, knowledgeStore: ServerKnowledgeStore, params: CreateAccountParams): Promise<CallToolResult>;
38
39
  export declare function handleCreateAccount(ynabAPI: ynab.API, params: CreateAccountParams): Promise<CallToolResult>;
39
40
  export declare const registerAccountTools: ToolFactory;
@@ -1,42 +1,42 @@
1
- import { z } from 'zod/v4';
2
- import { withToolErrorHandling } from '../types/index.js';
3
- import { responseFormatter } from '../server/responseFormatter.js';
4
- import { milliunitsToAmount } from '../utils/amountUtils.js';
5
- import { cacheManager, CACHE_TTLS, CacheManager } from '../server/cacheManager.js';
6
- import { CacheKeys } from '../server/cacheKeys.js';
7
- import { resolveDeltaFetcherArgs, resolveDeltaWriteArgs } from './deltaSupport.js';
8
- import { createAdapters, createBudgetResolver } from './adapters.js';
9
- import { ToolAnnotationPresets } from './toolCategories.js';
1
+ import { z } from "zod/v4";
2
+ import { CacheKeys } from "../server/cacheKeys.js";
3
+ import { CACHE_TTLS, CacheManager, cacheManager, } from "../server/cacheManager.js";
4
+ import { responseFormatter } from "../server/responseFormatter.js";
5
+ import { withToolErrorHandling } from "../types/index.js";
6
+ import { milliunitsToAmount } from "../utils/amountUtils.js";
7
+ import { createAdapters, createBudgetResolver } from "./adapters.js";
8
+ import { resolveDeltaFetcherArgs, resolveDeltaWriteArgs, } from "./deltaSupport.js";
9
+ import { ToolAnnotationPresets } from "./toolCategories.js";
10
10
  export const ListAccountsSchema = z
11
11
  .object({
12
- budget_id: z.string().min(1, 'Budget ID is required'),
12
+ budget_id: z.string().min(1, "Budget ID is required"),
13
13
  limit: z.number().int().positive().optional(),
14
14
  })
15
15
  .strict();
16
16
  export const GetAccountSchema = z
17
17
  .object({
18
- budget_id: z.string().min(1, 'Budget ID is required'),
19
- account_id: z.string().min(1, 'Account ID is required'),
18
+ budget_id: z.string().min(1, "Budget ID is required"),
19
+ account_id: z.string().min(1, "Account ID is required"),
20
20
  })
21
21
  .strict();
22
22
  export const CreateAccountSchema = z
23
23
  .object({
24
- budget_id: z.string().min(1, 'Budget ID is required'),
25
- name: z.string().min(1, 'Account name is required'),
24
+ budget_id: z.string().min(1, "Budget ID is required"),
25
+ name: z.string().min(1, "Account name is required"),
26
26
  type: z.enum([
27
- 'checking',
28
- 'savings',
29
- 'creditCard',
30
- 'cash',
31
- 'lineOfCredit',
32
- 'otherAsset',
33
- 'otherLiability',
27
+ "checking",
28
+ "savings",
29
+ "creditCard",
30
+ "cash",
31
+ "lineOfCredit",
32
+ "otherAsset",
33
+ "otherLiability",
34
34
  ]),
35
35
  balance: z.number().optional(),
36
36
  dry_run: z.boolean().optional(),
37
37
  })
38
38
  .strict();
39
- export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybeParams) {
39
+ export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybeParams, errorHandler) {
40
40
  const { deltaFetcher, params } = resolveDeltaFetcherArgs(ynabAPI, deltaFetcherOrParams, maybeParams);
41
41
  return await withToolErrorHandling(async () => {
42
42
  const result = await deltaFetcher.fetchAccounts(params.budget_id);
@@ -49,7 +49,7 @@ export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybePar
49
49
  return {
50
50
  content: [
51
51
  {
52
- type: 'text',
52
+ type: "text",
53
53
  text: responseFormatter.format({
54
54
  accounts: accounts.map((account) => ({
55
55
  id: account.id,
@@ -69,17 +69,17 @@ export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybePar
69
69
  returned_count: accounts.length,
70
70
  cached: wasCached,
71
71
  cache_info: wasCached
72
- ? `Data retrieved from cache for improved performance${result.usedDelta ? ' (delta merge applied)' : ''}`
73
- : 'Fresh data retrieved from YNAB API',
72
+ ? `Data retrieved from cache for improved performance${result.usedDelta ? " (delta merge applied)" : ""}`
73
+ : "Fresh data retrieved from YNAB API",
74
74
  }),
75
75
  },
76
76
  ],
77
77
  };
78
- }, 'ynab:list_accounts', 'listing accounts');
78
+ }, "ynab:list_accounts", "listing accounts", errorHandler);
79
79
  }
80
- export async function handleGetAccount(ynabAPI, params) {
80
+ export async function handleGetAccount(ynabAPI, params, errorHandler) {
81
81
  return await withToolErrorHandling(async () => {
82
- const cacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, 'get', params.budget_id, params.account_id);
82
+ const cacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, "get", params.budget_id, params.account_id);
83
83
  const wasCached = cacheManager.has(cacheKey);
84
84
  const account = await cacheManager.wrap(cacheKey, {
85
85
  ttl: CACHE_TTLS.ACCOUNTS,
@@ -91,7 +91,7 @@ export async function handleGetAccount(ynabAPI, params) {
91
91
  return {
92
92
  content: [
93
93
  {
94
- type: 'text',
94
+ type: "text",
95
95
  text: responseFormatter.format({
96
96
  account: {
97
97
  id: account.id,
@@ -109,25 +109,25 @@ export async function handleGetAccount(ynabAPI, params) {
109
109
  },
110
110
  cached: wasCached,
111
111
  cache_info: wasCached
112
- ? 'Data retrieved from cache for improved performance'
113
- : 'Fresh data retrieved from YNAB API',
112
+ ? "Data retrieved from cache for improved performance"
113
+ : "Fresh data retrieved from YNAB API",
114
114
  }),
115
115
  },
116
116
  ],
117
117
  };
118
- }, 'ynab:get_account', 'getting account details');
118
+ }, "ynab:get_account", "getting account details", errorHandler);
119
119
  }
120
- export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledgeStoreOrParams, maybeParams) {
120
+ export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledgeStoreOrParams, maybeParams, errorHandler) {
121
121
  const { deltaCache, params } = resolveDeltaWriteArgs(deltaCacheOrParams, knowledgeStoreOrParams, maybeParams);
122
122
  return await withToolErrorHandling(async () => {
123
123
  if (params.dry_run) {
124
124
  return {
125
125
  content: [
126
126
  {
127
- type: 'text',
127
+ type: "text",
128
128
  text: responseFormatter.format({
129
129
  dry_run: true,
130
- action: 'create_account',
130
+ action: "create_account",
131
131
  request: {
132
132
  budget_id: params.budget_id,
133
133
  name: params.name,
@@ -148,13 +148,13 @@ export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledge
148
148
  account: accountData,
149
149
  });
150
150
  const account = response.data.account;
151
- const accountsListCacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, 'list', params.budget_id);
151
+ const accountsListCacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, "list", params.budget_id);
152
152
  cacheManager.delete(accountsListCacheKey);
153
153
  deltaCache.invalidate(params.budget_id, CacheKeys.ACCOUNTS);
154
154
  return {
155
155
  content: [
156
156
  {
157
- type: 'text',
157
+ type: "text",
158
158
  text: responseFormatter.format({
159
159
  account: {
160
160
  id: account.id,
@@ -174,47 +174,47 @@ export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledge
174
174
  },
175
175
  ],
176
176
  };
177
- }, 'ynab:create_account', 'creating account');
177
+ }, "ynab:create_account", "creating account", errorHandler);
178
178
  }
179
179
  export const registerAccountTools = (registry, context) => {
180
180
  const { adapt, adaptWithDelta, adaptWrite } = createAdapters(context);
181
181
  const budgetResolver = createBudgetResolver(context);
182
182
  registry.register({
183
- name: 'list_accounts',
184
- description: 'List all accounts for a specific budget',
183
+ name: "list_accounts",
184
+ description: "List all accounts for a specific budget",
185
185
  inputSchema: ListAccountsSchema,
186
186
  handler: adaptWithDelta(handleListAccounts),
187
187
  defaultArgumentResolver: budgetResolver(),
188
188
  metadata: {
189
189
  annotations: {
190
190
  ...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
191
- title: 'YNAB: List Accounts',
191
+ title: "YNAB: List Accounts",
192
192
  },
193
193
  },
194
194
  });
195
195
  registry.register({
196
- name: 'get_account',
197
- description: 'Get detailed information for a specific account',
196
+ name: "get_account",
197
+ description: "Get detailed information for a specific account",
198
198
  inputSchema: GetAccountSchema,
199
199
  handler: adapt(handleGetAccount),
200
200
  defaultArgumentResolver: budgetResolver(),
201
201
  metadata: {
202
202
  annotations: {
203
203
  ...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
204
- title: 'YNAB: Get Account Details',
204
+ title: "YNAB: Get Account Details",
205
205
  },
206
206
  },
207
207
  });
208
208
  registry.register({
209
- name: 'create_account',
210
- description: 'Create a new account in the specified budget',
209
+ name: "create_account",
210
+ description: "Create a new account in the specified budget",
211
211
  inputSchema: CreateAccountSchema,
212
212
  handler: adaptWrite(handleCreateAccount),
213
213
  defaultArgumentResolver: budgetResolver(),
214
214
  metadata: {
215
215
  annotations: {
216
216
  ...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
217
- title: 'YNAB: Create Account',
217
+ title: "YNAB: Create Account",
218
218
  },
219
219
  },
220
220
  });