@dizzlkheinz/ynab-mcpb 0.18.4 → 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 (343) hide show
  1. package/CLAUDE.md +87 -8
  2. package/bin/ynab-mcp-server.cjs +2 -2
  3. package/bin/ynab-mcp-server.js +3 -3
  4. package/biome.json +39 -0
  5. package/dist/bundle/index.cjs +67 -67
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +27 -27
  8. package/dist/server/YNABMCPServer.d.ts +3 -4
  9. package/dist/server/YNABMCPServer.js +111 -116
  10. package/dist/server/budgetResolver.d.ts +6 -5
  11. package/dist/server/budgetResolver.js +46 -36
  12. package/dist/server/cacheKeys.js +6 -6
  13. package/dist/server/cacheManager.js +14 -11
  14. package/dist/server/completions.d.ts +2 -2
  15. package/dist/server/completions.js +20 -15
  16. package/dist/server/config.d.ts +10 -5
  17. package/dist/server/config.js +24 -7
  18. package/dist/server/deltaCache.d.ts +2 -2
  19. package/dist/server/deltaCache.js +22 -16
  20. package/dist/server/deltaCache.merge.d.ts +2 -2
  21. package/dist/server/diagnostics.d.ts +4 -4
  22. package/dist/server/diagnostics.js +38 -32
  23. package/dist/server/errorHandler.d.ts +5 -12
  24. package/dist/server/errorHandler.js +219 -217
  25. package/dist/server/prompts.d.ts +2 -2
  26. package/dist/server/prompts.js +45 -45
  27. package/dist/server/rateLimiter.js +4 -4
  28. package/dist/server/requestLogger.d.ts +1 -1
  29. package/dist/server/requestLogger.js +40 -35
  30. package/dist/server/resources.d.ts +3 -3
  31. package/dist/server/resources.js +55 -52
  32. package/dist/server/responseFormatter.js +6 -6
  33. package/dist/server/securityMiddleware.d.ts +2 -2
  34. package/dist/server/securityMiddleware.js +22 -20
  35. package/dist/server/serverKnowledgeStore.js +1 -1
  36. package/dist/server/toolRegistry.d.ts +3 -3
  37. package/dist/server/toolRegistry.js +47 -40
  38. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  39. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  40. package/dist/tools/accountTools.d.ts +9 -8
  41. package/dist/tools/accountTools.js +47 -47
  42. package/dist/tools/adapters.d.ts +13 -8
  43. package/dist/tools/adapters.js +21 -11
  44. package/dist/tools/budgetTools.d.ts +8 -7
  45. package/dist/tools/budgetTools.js +22 -22
  46. package/dist/tools/categoryTools.d.ts +9 -8
  47. package/dist/tools/categoryTools.js +68 -59
  48. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  49. package/dist/tools/compareTransactions/formatter.js +9 -9
  50. package/dist/tools/compareTransactions/index.d.ts +6 -6
  51. package/dist/tools/compareTransactions/index.js +58 -43
  52. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  53. package/dist/tools/compareTransactions/matcher.js +28 -15
  54. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  55. package/dist/tools/compareTransactions/parser.js +144 -138
  56. package/dist/tools/compareTransactions/types.d.ts +4 -4
  57. package/dist/tools/compareTransactions.d.ts +1 -1
  58. package/dist/tools/compareTransactions.js +1 -1
  59. package/dist/tools/deltaFetcher.d.ts +2 -2
  60. package/dist/tools/deltaFetcher.js +16 -15
  61. package/dist/tools/deltaSupport.d.ts +4 -4
  62. package/dist/tools/deltaSupport.js +35 -41
  63. package/dist/tools/exportTransactions.d.ts +5 -4
  64. package/dist/tools/exportTransactions.js +61 -59
  65. package/dist/tools/monthTools.d.ts +7 -6
  66. package/dist/tools/monthTools.js +31 -29
  67. package/dist/tools/payeeTools.d.ts +7 -6
  68. package/dist/tools/payeeTools.js +28 -28
  69. package/dist/tools/reconcileAdapter.d.ts +2 -2
  70. package/dist/tools/reconcileAdapter.js +19 -12
  71. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  72. package/dist/tools/reconciliation/analyzer.js +73 -59
  73. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  74. package/dist/tools/reconciliation/csvParser.js +128 -104
  75. package/dist/tools/reconciliation/executor.d.ts +4 -4
  76. package/dist/tools/reconciliation/executor.js +148 -109
  77. package/dist/tools/reconciliation/index.d.ts +10 -10
  78. package/dist/tools/reconciliation/index.js +96 -83
  79. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  80. package/dist/tools/reconciliation/matcher.js +17 -16
  81. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  82. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  83. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  84. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  85. package/dist/tools/reconciliation/reportFormatter.js +59 -58
  86. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  87. package/dist/tools/reconciliation/types.d.ts +16 -16
  88. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  89. package/dist/tools/schemas/common.d.ts +1 -1
  90. package/dist/tools/schemas/common.js +1 -1
  91. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  93. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  94. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  95. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  96. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  97. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  98. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  99. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  100. package/dist/tools/schemas/outputs/index.js +14 -14
  101. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  102. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  103. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  104. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  105. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  107. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  109. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  110. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  111. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  112. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  113. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  114. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  115. package/dist/tools/transactionReadTools.d.ts +11 -0
  116. package/dist/tools/transactionReadTools.js +202 -0
  117. package/dist/tools/transactionSchemas.d.ts +7 -7
  118. package/dist/tools/transactionSchemas.js +77 -57
  119. package/dist/tools/transactionTools.d.ts +6 -24
  120. package/dist/tools/transactionTools.js +7 -1499
  121. package/dist/tools/transactionUtils.d.ts +6 -6
  122. package/dist/tools/transactionUtils.js +78 -63
  123. package/dist/tools/transactionWriteTools.d.ts +20 -0
  124. package/dist/tools/transactionWriteTools.js +1342 -0
  125. package/dist/tools/utilityTools.d.ts +5 -4
  126. package/dist/tools/utilityTools.js +11 -11
  127. package/dist/types/index.d.ts +7 -7
  128. package/dist/types/index.js +6 -6
  129. package/dist/types/reconciliation.d.ts +1 -1
  130. package/dist/types/toolRegistration.d.ts +14 -12
  131. package/dist/utils/amountUtils.js +1 -1
  132. package/dist/utils/dateUtils.js +4 -4
  133. package/dist/utils/errors.d.ts +3 -3
  134. package/dist/utils/errors.js +4 -4
  135. package/dist/utils/money.d.ts +2 -2
  136. package/dist/utils/money.js +8 -8
  137. package/dist/utils/validationError.d.ts +1 -1
  138. package/dist/utils/validationError.js +1 -1
  139. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  140. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  141. package/esbuild.config.mjs +53 -50
  142. package/meta.json +12548 -12548
  143. package/package.json +98 -111
  144. package/scripts/analyze-bundle.mjs +33 -30
  145. package/scripts/create-pr-description.js +169 -120
  146. package/scripts/run-all-tests.js +178 -169
  147. package/scripts/run-domain-integration-tests.js +28 -18
  148. package/scripts/run-generate-mcpb.js +19 -17
  149. package/scripts/run-throttled-integration-tests.js +92 -83
  150. package/scripts/test-delta-params.mjs +149 -120
  151. package/scripts/test-recommendations.ts +36 -32
  152. package/scripts/tmpTransaction.ts +80 -43
  153. package/scripts/validate-env.js +98 -91
  154. package/scripts/verify-build.js +78 -76
  155. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  156. package/src/__tests__/performance.test.ts +723 -671
  157. package/src/__tests__/setup.ts +442 -395
  158. package/src/__tests__/smoke.e2e.test.ts +41 -39
  159. package/src/__tests__/testRunner.ts +314 -295
  160. package/src/__tests__/testUtils.ts +456 -364
  161. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  162. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  163. package/src/index.ts +68 -59
  164. package/src/server/CLAUDE.md +480 -0
  165. package/src/server/YNABMCPServer.ts +821 -794
  166. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  167. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  168. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  169. package/src/server/__tests__/cacheManager.test.ts +891 -874
  170. package/src/server/__tests__/completions.integration.test.ts +115 -106
  171. package/src/server/__tests__/completions.test.ts +334 -313
  172. package/src/server/__tests__/config.test.ts +98 -86
  173. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  174. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  175. package/src/server/__tests__/deltaCache.test.ts +946 -759
  176. package/src/server/__tests__/diagnostics.test.ts +825 -792
  177. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  178. package/src/server/__tests__/errorHandler.test.ts +402 -397
  179. package/src/server/__tests__/prompts.test.ts +424 -347
  180. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  181. package/src/server/__tests__/requestLogger.test.ts +443 -403
  182. package/src/server/__tests__/resources.template.test.ts +196 -185
  183. package/src/server/__tests__/resources.test.ts +294 -288
  184. package/src/server/__tests__/security.integration.test.ts +487 -421
  185. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  186. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  187. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  188. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  189. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  190. package/src/server/budgetResolver.ts +221 -181
  191. package/src/server/cacheKeys.ts +6 -6
  192. package/src/server/cacheManager.ts +498 -484
  193. package/src/server/completions.ts +267 -243
  194. package/src/server/config.ts +35 -14
  195. package/src/server/deltaCache.merge.ts +146 -128
  196. package/src/server/deltaCache.ts +352 -309
  197. package/src/server/diagnostics.ts +257 -242
  198. package/src/server/errorHandler.ts +747 -744
  199. package/src/server/prompts.ts +181 -176
  200. package/src/server/rateLimiter.ts +131 -129
  201. package/src/server/requestLogger.ts +350 -322
  202. package/src/server/resources.ts +442 -374
  203. package/src/server/responseFormatter.ts +41 -37
  204. package/src/server/securityMiddleware.ts +223 -205
  205. package/src/server/serverKnowledgeStore.ts +67 -67
  206. package/src/server/toolRegistry.ts +508 -474
  207. package/src/tools/CLAUDE.md +604 -0
  208. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  209. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  210. package/src/tools/__tests__/accountTools.test.ts +685 -638
  211. package/src/tools/__tests__/adapters.test.ts +142 -108
  212. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  213. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  214. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  215. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  216. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  217. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  218. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  219. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  220. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  221. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  222. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  223. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  224. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  225. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  226. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  227. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  228. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  229. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  230. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  231. package/src/tools/__tests__/monthTools.test.ts +561 -512
  232. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  234. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  235. package/src/tools/__tests__/transactionSchemas.test.ts +1202 -1186
  236. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  237. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  238. package/src/tools/__tests__/transactionUtils.test.ts +1004 -977
  239. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  240. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  241. package/src/tools/accountTools.ts +293 -271
  242. package/src/tools/adapters.ts +120 -63
  243. package/src/tools/budgetTools.ts +121 -116
  244. package/src/tools/categoryTools.ts +379 -339
  245. package/src/tools/compareTransactions/formatter.ts +131 -119
  246. package/src/tools/compareTransactions/index.ts +249 -214
  247. package/src/tools/compareTransactions/matcher.ts +259 -209
  248. package/src/tools/compareTransactions/parser.ts +517 -487
  249. package/src/tools/compareTransactions/types.ts +38 -38
  250. package/src/tools/compareTransactions.ts +1 -1
  251. package/src/tools/deltaFetcher.ts +281 -260
  252. package/src/tools/deltaSupport.ts +264 -259
  253. package/src/tools/exportTransactions.ts +230 -218
  254. package/src/tools/monthTools.ts +180 -165
  255. package/src/tools/payeeTools.ts +152 -140
  256. package/src/tools/reconcileAdapter.ts +297 -252
  257. package/src/tools/reconciliation/CLAUDE.md +506 -0
  258. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +133 -124
  259. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -230
  260. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -400
  261. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  262. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  263. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  264. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  265. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  266. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  267. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  268. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -989
  269. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  270. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -533
  271. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -74
  272. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -62
  273. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  274. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +56 -55
  275. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  276. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  277. package/src/tools/reconciliation/analyzer.ts +564 -504
  278. package/src/tools/reconciliation/csvParser.ts +656 -609
  279. package/src/tools/reconciliation/executor.ts +1290 -1128
  280. package/src/tools/reconciliation/index.ts +580 -528
  281. package/src/tools/reconciliation/matcher.ts +256 -240
  282. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  283. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  284. package/src/tools/reconciliation/reportFormatter.ts +343 -307
  285. package/src/tools/reconciliation/signDetector.ts +89 -83
  286. package/src/tools/reconciliation/types.ts +164 -159
  287. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  288. package/src/tools/schemas/CLAUDE.md +546 -0
  289. package/src/tools/schemas/common.ts +1 -1
  290. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  291. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  292. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  293. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  294. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  295. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  296. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  297. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  298. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  299. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  300. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  301. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  302. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  303. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  304. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  305. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  306. package/src/tools/schemas/outputs/index.ts +163 -163
  307. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  308. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  309. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  310. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  311. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  312. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  313. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  314. package/src/tools/toolCategories.ts +114 -114
  315. package/src/tools/transactionReadTools.ts +327 -0
  316. package/src/tools/transactionSchemas.ts +322 -291
  317. package/src/tools/transactionTools.ts +84 -2246
  318. package/src/tools/transactionUtils.ts +507 -422
  319. package/src/tools/transactionWriteTools.ts +2110 -0
  320. package/src/tools/utilityTools.ts +46 -41
  321. package/src/types/CLAUDE.md +477 -0
  322. package/src/types/__tests__/index.test.ts +51 -51
  323. package/src/types/index.ts +43 -39
  324. package/src/types/integration-tests.d.ts +26 -26
  325. package/src/types/reconciliation.ts +29 -29
  326. package/src/types/toolAnnotations.ts +30 -30
  327. package/src/types/toolRegistration.ts +43 -32
  328. package/src/utils/CLAUDE.md +508 -0
  329. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  330. package/src/utils/__tests__/money.test.ts +193 -187
  331. package/src/utils/amountUtils.ts +5 -5
  332. package/src/utils/baseError.ts +5 -5
  333. package/src/utils/dateUtils.ts +29 -26
  334. package/src/utils/errors.ts +14 -14
  335. package/src/utils/money.ts +66 -52
  336. package/src/utils/validationError.ts +1 -1
  337. package/tsconfig.json +29 -29
  338. package/tsconfig.prod.json +16 -16
  339. package/vitest-reporters/split-json-reporter.ts +247 -204
  340. package/vitest.config.ts +99 -95
  341. package/.prettierignore +0 -10
  342. package/.prettierrc.json +0 -10
  343. 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
  });