@dizzlkheinz/ynab-mcpb 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/.chunkhound.json +11 -0
  2. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
  3. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
  4. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
  5. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
  6. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
  7. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
  8. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
  9. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
  10. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
  11. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
  12. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
  13. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
  14. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
  15. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
  16. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
  17. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
  18. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
  19. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
  20. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
  21. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
  22. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
  23. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
  24. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
  25. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
  26. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
  27. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
  28. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
  29. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
  30. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
  31. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
  32. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
  33. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
  34. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
  35. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
  36. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
  37. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
  38. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
  39. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
  40. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
  41. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
  42. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
  43. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
  44. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
  45. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
  46. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
  47. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
  48. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
  49. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
  50. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
  51. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
  52. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
  53. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
  54. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
  55. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
  56. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
  57. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
  58. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
  59. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
  60. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
  61. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
  62. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
  63. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
  64. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
  65. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
  66. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
  67. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
  68. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
  69. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
  70. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
  71. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
  72. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
  73. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
  74. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
  75. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
  76. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
  77. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
  78. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
  79. package/.dxtignore +57 -0
  80. package/.env.example +44 -0
  81. package/.gemini/settings.json +8 -0
  82. package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  83. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  84. package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  85. package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
  86. package/.github/pull_request_template.md +41 -0
  87. package/.github/workflows/ci-tests.yml +41 -0
  88. package/.github/workflows/claude-code-review.yml +57 -0
  89. package/.github/workflows/claude.yml +50 -0
  90. package/.github/workflows/full-integration.yml +22 -0
  91. package/.github/workflows/pr-description-check.yml +88 -0
  92. package/.github/workflows/publish.yml +33 -0
  93. package/.github/workflows/release.yml +89 -0
  94. package/.mcpbignore +58 -0
  95. package/.prettierignore +10 -0
  96. package/.prettierrc.json +10 -0
  97. package/ADOS-2-Module-1-Complete-Manual.md +757 -0
  98. package/AGENTS.md +36 -0
  99. package/CHANGELOG.md +187 -0
  100. package/CLAUDE.md +414 -0
  101. package/CODEREVIEW_RESPONSE.md +128 -0
  102. package/LICENSE +17 -0
  103. package/NUL +1 -0
  104. package/README.md +222 -0
  105. package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
  106. package/TESTING_NOTES.md +217 -0
  107. package/WARP.md +245 -0
  108. package/accountactivity-merged.csv +149 -0
  109. package/bin/ynab-mcp-server.cjs +4 -0
  110. package/bin/ynab-mcp-server.js +8 -0
  111. package/bundle-analysis.html +13110 -0
  112. package/dist/bundle/index.cjs +124 -0
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.js +85 -0
  115. package/dist/server/YNABMCPServer.d.ts +264 -0
  116. package/dist/server/YNABMCPServer.js +845 -0
  117. package/dist/server/budgetResolver.d.ts +15 -0
  118. package/dist/server/budgetResolver.js +99 -0
  119. package/dist/server/cacheManager.d.ts +74 -0
  120. package/dist/server/cacheManager.js +306 -0
  121. package/dist/server/config.d.ts +3 -0
  122. package/dist/server/config.js +19 -0
  123. package/dist/server/deltaCache.d.ts +61 -0
  124. package/dist/server/deltaCache.js +206 -0
  125. package/dist/server/deltaCache.merge.d.ts +9 -0
  126. package/dist/server/deltaCache.merge.js +111 -0
  127. package/dist/server/diagnostics.d.ts +90 -0
  128. package/dist/server/diagnostics.js +163 -0
  129. package/dist/server/errorHandler.d.ts +69 -0
  130. package/dist/server/errorHandler.js +524 -0
  131. package/dist/server/prompts.d.ts +31 -0
  132. package/dist/server/prompts.js +205 -0
  133. package/dist/server/rateLimiter.d.ts +27 -0
  134. package/dist/server/rateLimiter.js +82 -0
  135. package/dist/server/requestLogger.d.ts +62 -0
  136. package/dist/server/requestLogger.js +190 -0
  137. package/dist/server/resources.d.ts +39 -0
  138. package/dist/server/resources.js +85 -0
  139. package/dist/server/responseFormatter.d.ts +14 -0
  140. package/dist/server/responseFormatter.js +42 -0
  141. package/dist/server/securityMiddleware.d.ts +87 -0
  142. package/dist/server/securityMiddleware.js +117 -0
  143. package/dist/server/serverKnowledgeStore.d.ts +11 -0
  144. package/dist/server/serverKnowledgeStore.js +42 -0
  145. package/dist/server/toolRegistry.d.ts +85 -0
  146. package/dist/server/toolRegistry.js +272 -0
  147. package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
  148. package/dist/tools/__tests__/deltaTestUtils.js +26 -0
  149. package/dist/tools/accountTools.d.ts +37 -0
  150. package/dist/tools/accountTools.js +175 -0
  151. package/dist/tools/budgetTools.d.ts +10 -0
  152. package/dist/tools/budgetTools.js +68 -0
  153. package/dist/tools/categoryTools.d.ts +27 -0
  154. package/dist/tools/categoryTools.js +232 -0
  155. package/dist/tools/compareTransactions/formatter.d.ts +71 -0
  156. package/dist/tools/compareTransactions/formatter.js +97 -0
  157. package/dist/tools/compareTransactions/index.d.ts +30 -0
  158. package/dist/tools/compareTransactions/index.js +160 -0
  159. package/dist/tools/compareTransactions/matcher.d.ts +12 -0
  160. package/dist/tools/compareTransactions/matcher.js +140 -0
  161. package/dist/tools/compareTransactions/parser.d.ts +14 -0
  162. package/dist/tools/compareTransactions/parser.js +430 -0
  163. package/dist/tools/compareTransactions/types.d.ts +27 -0
  164. package/dist/tools/compareTransactions/types.js +1 -0
  165. package/dist/tools/compareTransactions.d.ts +1 -0
  166. package/dist/tools/compareTransactions.js +1 -0
  167. package/dist/tools/deltaFetcher.d.ts +22 -0
  168. package/dist/tools/deltaFetcher.js +137 -0
  169. package/dist/tools/deltaSupport.d.ts +20 -0
  170. package/dist/tools/deltaSupport.js +176 -0
  171. package/dist/tools/exportTransactions.d.ts +17 -0
  172. package/dist/tools/exportTransactions.js +191 -0
  173. package/dist/tools/monthTools.d.ts +16 -0
  174. package/dist/tools/monthTools.js +107 -0
  175. package/dist/tools/payeeTools.d.ts +17 -0
  176. package/dist/tools/payeeTools.js +82 -0
  177. package/dist/tools/reconcileAdapter.d.ts +25 -0
  178. package/dist/tools/reconcileAdapter.js +167 -0
  179. package/dist/tools/reconciliation/analyzer.d.ts +3 -0
  180. package/dist/tools/reconciliation/analyzer.js +567 -0
  181. package/dist/tools/reconciliation/executor.d.ts +94 -0
  182. package/dist/tools/reconciliation/executor.js +611 -0
  183. package/dist/tools/reconciliation/index.d.ts +54 -0
  184. package/dist/tools/reconciliation/index.js +249 -0
  185. package/dist/tools/reconciliation/matcher.d.ts +3 -0
  186. package/dist/tools/reconciliation/matcher.js +160 -0
  187. package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
  188. package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
  189. package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
  190. package/dist/tools/reconciliation/recommendationEngine.js +273 -0
  191. package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
  192. package/dist/tools/reconciliation/reportFormatter.js +214 -0
  193. package/dist/tools/reconciliation/types.d.ts +172 -0
  194. package/dist/tools/reconciliation/types.js +7 -0
  195. package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
  196. package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
  197. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
  198. package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
  199. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
  200. package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
  201. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
  202. package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
  203. package/dist/tools/schemas/outputs/index.d.ts +14 -0
  204. package/dist/tools/schemas/outputs/index.js +14 -0
  205. package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
  206. package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
  207. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
  208. package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
  209. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
  210. package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
  211. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
  212. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
  213. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
  214. package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
  215. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
  216. package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
  217. package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
  218. package/dist/tools/schemas/shared/commonOutputs.js +27 -0
  219. package/dist/tools/toolCategories.d.ts +32 -0
  220. package/dist/tools/toolCategories.js +32 -0
  221. package/dist/tools/transactionTools.d.ts +315 -0
  222. package/dist/tools/transactionTools.js +1722 -0
  223. package/dist/tools/utilityTools.d.ts +10 -0
  224. package/dist/tools/utilityTools.js +56 -0
  225. package/dist/types/index.d.ts +20 -0
  226. package/dist/types/index.js +16 -0
  227. package/dist/types/toolAnnotations.d.ts +7 -0
  228. package/dist/types/toolAnnotations.js +1 -0
  229. package/dist/utils/amountUtils.d.ts +3 -0
  230. package/dist/utils/amountUtils.js +10 -0
  231. package/dist/utils/dateUtils.d.ts +9 -0
  232. package/dist/utils/dateUtils.js +43 -0
  233. package/dist/utils/money.d.ts +21 -0
  234. package/dist/utils/money.js +51 -0
  235. package/docs/README.md +72 -0
  236. package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
  237. package/docs/assets/schemas/reconciliation-v2.json +338 -0
  238. package/docs/getting-started/CONFIGURATION.md +175 -0
  239. package/docs/getting-started/INSTALLATION.md +333 -0
  240. package/docs/getting-started/QUICKSTART.md +282 -0
  241. package/docs/guides/ARCHITECTURE.md +650 -0
  242. package/docs/guides/DEPLOYMENT.md +189 -0
  243. package/docs/guides/INTEGRATION_TESTING.md +730 -0
  244. package/docs/guides/TESTING.md +591 -0
  245. package/docs/reconciliation-flow.md +83 -0
  246. package/docs/reference/API.md +1450 -0
  247. package/docs/reference/EXAMPLES.md +946 -0
  248. package/docs/reference/TOOLS.md +348 -0
  249. package/docs/reference/TROUBLESHOOTING.md +481 -0
  250. package/esbuild.config.mjs +68 -0
  251. package/eslint.config.js +49 -0
  252. package/fix-types.sh +17 -0
  253. package/meta.json +12550 -0
  254. package/package.json +105 -0
  255. package/package.json.tmp +105 -0
  256. package/scripts/analyze-bundle.mjs +41 -0
  257. package/scripts/create-pr-description.js +203 -0
  258. package/scripts/generate-mcpb.ps1 +96 -0
  259. package/scripts/run-domain-integration-tests.js +33 -0
  260. package/scripts/run-generate-mcpb.js +29 -0
  261. package/scripts/run-throttled-integration-tests.js +116 -0
  262. package/scripts/test-delta-params.mjs +140 -0
  263. package/scripts/test-recommendations.ts +53 -0
  264. package/scripts/tmpTransaction.ts +48 -0
  265. package/scripts/validate-env.js +122 -0
  266. package/scripts/verify-build.js +105 -0
  267. package/scripts/watch-and-restart.ps1 +50 -0
  268. package/src/__tests__/comprehensive.integration.test.ts +1196 -0
  269. package/src/__tests__/delta.performance.test.ts +80 -0
  270. package/src/__tests__/performance.test.ts +725 -0
  271. package/src/__tests__/setup.ts +449 -0
  272. package/src/__tests__/testRunner.ts +444 -0
  273. package/src/__tests__/testUtils.ts +563 -0
  274. package/src/__tests__/workflows.e2e.test.ts +1675 -0
  275. package/src/index.ts +124 -0
  276. package/src/server/.gitkeep +1 -0
  277. package/src/server/YNABMCPServer.ts +1188 -0
  278. package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
  279. package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
  280. package/src/server/__tests__/budgetResolver.test.ts +425 -0
  281. package/src/server/__tests__/cacheManager.test.ts +880 -0
  282. package/src/server/__tests__/config.test.ts +166 -0
  283. package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
  284. package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
  285. package/src/server/__tests__/deltaCache.test.ts +774 -0
  286. package/src/server/__tests__/diagnostics.test.ts +823 -0
  287. package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
  288. package/src/server/__tests__/errorHandler.test.ts +416 -0
  289. package/src/server/__tests__/prompts.test.ts +354 -0
  290. package/src/server/__tests__/rateLimiter.test.ts +314 -0
  291. package/src/server/__tests__/requestLogger.test.ts +408 -0
  292. package/src/server/__tests__/resources.test.ts +299 -0
  293. package/src/server/__tests__/security.integration.test.ts +426 -0
  294. package/src/server/__tests__/securityMiddleware.test.ts +449 -0
  295. package/src/server/__tests__/server-startup.integration.test.ts +477 -0
  296. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
  297. package/src/server/__tests__/toolRegistry.test.ts +855 -0
  298. package/src/server/budgetResolver.ts +235 -0
  299. package/src/server/cacheManager.ts +503 -0
  300. package/src/server/config.ts +41 -0
  301. package/src/server/deltaCache.merge.ts +149 -0
  302. package/src/server/deltaCache.ts +341 -0
  303. package/src/server/diagnostics.ts +338 -0
  304. package/src/server/errorHandler.ts +756 -0
  305. package/src/server/prompts.ts +291 -0
  306. package/src/server/rateLimiter.ts +156 -0
  307. package/src/server/requestLogger.ts +344 -0
  308. package/src/server/resources.ts +168 -0
  309. package/src/server/responseFormatter.ts +51 -0
  310. package/src/server/securityMiddleware.ts +236 -0
  311. package/src/server/serverKnowledgeStore.ts +91 -0
  312. package/src/server/toolRegistry.ts +489 -0
  313. package/src/tools/.gitkeep +1 -0
  314. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
  315. package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
  316. package/src/tools/__tests__/accountTools.test.ts +653 -0
  317. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
  318. package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
  319. package/src/tools/__tests__/budgetTools.test.ts +423 -0
  320. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
  321. package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
  322. package/src/tools/__tests__/categoryTools.test.ts +622 -0
  323. package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
  324. package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
  325. package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
  326. package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
  327. package/src/tools/__tests__/compareTransactions.test.ts +342 -0
  328. package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
  329. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
  330. package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
  331. package/src/tools/__tests__/deltaSupport.test.ts +188 -0
  332. package/src/tools/__tests__/deltaTestUtils.ts +46 -0
  333. package/src/tools/__tests__/exportTransactions.test.ts +213 -0
  334. package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
  335. package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
  336. package/src/tools/__tests__/monthTools.test.ts +523 -0
  337. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
  338. package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
  339. package/src/tools/__tests__/payeeTools.test.ts +445 -0
  340. package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
  341. package/src/tools/__tests__/transactionTools.test.ts +3521 -0
  342. package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
  343. package/src/tools/__tests__/utilityTools.test.ts +205 -0
  344. package/src/tools/accountTools.ts +283 -0
  345. package/src/tools/budgetTools.ts +112 -0
  346. package/src/tools/categoryTools.ts +366 -0
  347. package/src/tools/compareTransactions/formatter.ts +163 -0
  348. package/src/tools/compareTransactions/index.ts +228 -0
  349. package/src/tools/compareTransactions/matcher.ts +240 -0
  350. package/src/tools/compareTransactions/parser.ts +557 -0
  351. package/src/tools/compareTransactions/types.ts +60 -0
  352. package/src/tools/compareTransactions.ts +3 -0
  353. package/src/tools/deltaFetcher.ts +278 -0
  354. package/src/tools/deltaSupport.ts +293 -0
  355. package/src/tools/exportTransactions.ts +273 -0
  356. package/src/tools/monthTools.ts +164 -0
  357. package/src/tools/payeeTools.ts +140 -0
  358. package/src/tools/reconcileAdapter.ts +312 -0
  359. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
  360. package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
  361. package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
  362. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
  363. package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
  364. package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
  365. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
  366. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
  367. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
  368. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
  369. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
  370. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
  371. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
  372. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
  373. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
  374. package/src/tools/reconciliation/analyzer.ts +824 -0
  375. package/src/tools/reconciliation/executor.ts +880 -0
  376. package/src/tools/reconciliation/index.ts +400 -0
  377. package/src/tools/reconciliation/matcher.ts +269 -0
  378. package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
  379. package/src/tools/reconciliation/recommendationEngine.ts +506 -0
  380. package/src/tools/reconciliation/reportFormatter.ts +363 -0
  381. package/src/tools/reconciliation/types.ts +314 -0
  382. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
  383. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
  384. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
  385. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
  386. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
  387. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
  388. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
  389. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
  390. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
  391. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
  392. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
  393. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
  394. package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
  395. package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
  396. package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
  397. package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
  398. package/src/tools/schemas/outputs/index.ts +270 -0
  399. package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
  400. package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
  401. package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
  402. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
  403. package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
  404. package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
  405. package/src/tools/schemas/shared/commonOutputs.ts +140 -0
  406. package/src/tools/toolCategories.ts +140 -0
  407. package/src/tools/transactionTools.ts +2509 -0
  408. package/src/tools/utilityTools.ts +90 -0
  409. package/src/types/.gitkeep +1 -0
  410. package/src/types/__tests__/index.test.ts +52 -0
  411. package/src/types/index.ts +67 -0
  412. package/src/types/integration-tests.d.ts +35 -0
  413. package/src/types/toolAnnotations.ts +44 -0
  414. package/src/utils/__tests__/dateUtils.test.ts +170 -0
  415. package/src/utils/__tests__/money.test.ts +189 -0
  416. package/src/utils/amountUtils.ts +32 -0
  417. package/src/utils/dateUtils.ts +108 -0
  418. package/src/utils/money.ts +123 -0
  419. package/test-csv-sample.csv +28 -0
  420. package/test-exports/sample_bank_statement.csv +7 -0
  421. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
  422. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
  423. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
  424. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
  425. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
  426. package/test-reconcile-autodetect.js +40 -0
  427. package/test-reconcile-tool.js +152 -0
  428. package/test-reconcile-with-csv.cjs +89 -0
  429. package/test-statement.csv +8 -0
  430. package/test_debug.js +47 -0
  431. package/test_simple.mjs +16 -0
  432. package/tsconfig.json +31 -0
  433. package/tsconfig.prod.json +18 -0
  434. package/vitest-reporters/split-json-reporter.ts +211 -0
  435. package/vitest.config.ts +96 -0
@@ -0,0 +1,14 @@
1
+ declare class ResponseFormatter {
2
+ private defaultMinify;
3
+ private prettySpaces;
4
+ private als;
5
+ constructor();
6
+ configure(options?: {
7
+ defaultMinify?: boolean;
8
+ prettySpaces?: number;
9
+ }): void;
10
+ runWithMinifyOverride<T>(minify: boolean | undefined, fn: () => T): T;
11
+ format(value: unknown): string;
12
+ }
13
+ export declare const responseFormatter: ResponseFormatter;
14
+ export {};
@@ -0,0 +1,42 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ function parseBool(value, fallback) {
3
+ if (value === undefined)
4
+ return fallback;
5
+ const v = value.trim().toLowerCase();
6
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
7
+ }
8
+ function parseIntSafe(value, fallback) {
9
+ if (value === undefined)
10
+ return fallback;
11
+ const n = Number.parseInt(value, 10);
12
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
13
+ }
14
+ class ResponseFormatter {
15
+ constructor() {
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);
19
+ }
20
+ configure(options) {
21
+ if (!options)
22
+ return;
23
+ if (typeof options.defaultMinify === 'boolean')
24
+ this.defaultMinify = options.defaultMinify;
25
+ if (typeof options.prettySpaces === 'number' && options.prettySpaces >= 0) {
26
+ this.prettySpaces = options.prettySpaces;
27
+ }
28
+ }
29
+ runWithMinifyOverride(minify, fn) {
30
+ if (minify === undefined)
31
+ return fn();
32
+ return this.als.run({ minify }, fn);
33
+ }
34
+ format(value) {
35
+ const ctx = this.als.getStore();
36
+ const minify = ctx?.minify ?? this.defaultMinify;
37
+ if (minify)
38
+ return JSON.stringify(value);
39
+ return JSON.stringify(value, null, this.prettySpaces);
40
+ }
41
+ }
42
+ export const responseFormatter = new ResponseFormatter();
@@ -0,0 +1,87 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { z } from 'zod/v4';
3
+ export interface SecurityContext {
4
+ accessToken: string;
5
+ toolName: string;
6
+ operation: string;
7
+ parameters: Record<string, unknown>;
8
+ startTime: number;
9
+ }
10
+ export declare class SecurityMiddleware {
11
+ static withSecurity<T extends Record<string, unknown>>(context: SecurityContext, schema: z.ZodSchema<T>, operation: (..._args: unknown[]) => Promise<CallToolResult>): Promise<CallToolResult>;
12
+ private static validateInput;
13
+ private static checkRateLimit;
14
+ private static createRateLimitErrorResponse;
15
+ private static hashToken;
16
+ static getSecurityStats(): {
17
+ rateLimitStats: Record<string, unknown>;
18
+ requestStats: Record<string, unknown>;
19
+ };
20
+ static reset(): void;
21
+ }
22
+ export declare function withSecurityWrapper<T extends Record<string, unknown>>(toolName: string, operation: string, schema: z.ZodSchema<T>): (accessToken: string) => (params: Record<string, unknown>) => (handler: (validated: T) => Promise<CallToolResult>) => Promise<{
23
+ [x: string]: unknown;
24
+ content: ({
25
+ type: "text";
26
+ text: string;
27
+ _meta?: {
28
+ [x: string]: unknown;
29
+ } | undefined;
30
+ } | {
31
+ type: "image";
32
+ data: string;
33
+ mimeType: string;
34
+ _meta?: {
35
+ [x: string]: unknown;
36
+ } | undefined;
37
+ } | {
38
+ type: "audio";
39
+ data: string;
40
+ mimeType: string;
41
+ _meta?: {
42
+ [x: string]: unknown;
43
+ } | undefined;
44
+ } | {
45
+ type: "resource_link";
46
+ name: string;
47
+ uri: string;
48
+ _meta?: {
49
+ [x: string]: unknown;
50
+ } | undefined;
51
+ mimeType?: string | undefined | undefined;
52
+ icons?: {
53
+ src: string;
54
+ mimeType?: string | undefined | undefined;
55
+ sizes?: string[] | undefined;
56
+ }[] | undefined;
57
+ title?: string | undefined | undefined;
58
+ description?: string | undefined | undefined;
59
+ } | {
60
+ type: "resource";
61
+ resource: {
62
+ uri: string;
63
+ text: string;
64
+ _meta?: {
65
+ [x: string]: unknown;
66
+ } | undefined;
67
+ mimeType?: string | undefined | undefined;
68
+ } | {
69
+ uri: string;
70
+ blob: string;
71
+ _meta?: {
72
+ [x: string]: unknown;
73
+ } | undefined;
74
+ mimeType?: string | undefined | undefined;
75
+ };
76
+ _meta?: {
77
+ [x: string]: unknown;
78
+ } | undefined;
79
+ })[];
80
+ _meta?: {
81
+ [x: string]: unknown;
82
+ } | undefined;
83
+ structuredContent?: {
84
+ [x: string]: unknown;
85
+ } | undefined;
86
+ isError?: boolean | undefined;
87
+ }>;
@@ -0,0 +1,117 @@
1
+ import { z } from 'zod/v4';
2
+ import { globalRateLimiter, RateLimitError } from './rateLimiter.js';
3
+ import { globalRequestLogger } from './requestLogger.js';
4
+ import { ErrorHandler } from './errorHandler.js';
5
+ import { responseFormatter } from './responseFormatter.js';
6
+ export class SecurityMiddleware {
7
+ static async withSecurity(context, schema, operation) {
8
+ const startTime = Date.now();
9
+ try {
10
+ const validatedParams = await this.validateInput(schema, context.parameters);
11
+ await this.checkRateLimit(context.accessToken);
12
+ globalRateLimiter.recordRequest(this.hashToken(context.accessToken));
13
+ const result = await operation(validatedParams);
14
+ const duration = Date.now() - startTime;
15
+ const rateLimitInfo = globalRateLimiter.getStatus(this.hashToken(context.accessToken));
16
+ globalRequestLogger.logSuccess(context.toolName, context.operation, context.parameters, duration, {
17
+ remaining: rateLimitInfo.remaining,
18
+ isLimited: rateLimitInfo.isLimited,
19
+ });
20
+ return result;
21
+ }
22
+ catch (error) {
23
+ const duration = Date.now() - startTime;
24
+ const rateLimitInfo = globalRateLimiter.getStatus(this.hashToken(context.accessToken));
25
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
26
+ globalRequestLogger.logError(context.toolName, context.operation, context.parameters, errorMessage, duration, {
27
+ remaining: rateLimitInfo.remaining,
28
+ isLimited: rateLimitInfo.isLimited,
29
+ });
30
+ if (error instanceof RateLimitError) {
31
+ return this.createRateLimitErrorResponse(error);
32
+ }
33
+ if (error instanceof Error && error.message.includes('Validation failed')) {
34
+ return ErrorHandler.createValidationError('Invalid parameters for ' + context.toolName, error.message);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ static async validateInput(schema, parameters) {
40
+ try {
41
+ return schema.parse(parameters);
42
+ }
43
+ catch (error) {
44
+ if (error instanceof z.ZodError) {
45
+ const errorMessage = error.issues && error.issues.length > 0
46
+ ? error.issues
47
+ .map((err) => `${err.path.join('.')}: ${err.message}`)
48
+ .join(', ')
49
+ : error.message || 'Validation failed';
50
+ throw new Error(`Validation failed: ${errorMessage}`);
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ static async checkRateLimit(accessToken) {
56
+ const tokenHash = this.hashToken(accessToken);
57
+ const rateLimitInfo = globalRateLimiter.isAllowed(tokenHash);
58
+ if (rateLimitInfo.isLimited) {
59
+ throw new RateLimitError('Rate limit exceeded. Please wait before making additional requests.', rateLimitInfo.resetTime, rateLimitInfo.remaining);
60
+ }
61
+ }
62
+ static createRateLimitErrorResponse(error) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: 'text',
67
+ text: responseFormatter.format({
68
+ error: {
69
+ code: 'RATE_LIMIT_EXCEEDED',
70
+ message: error.message,
71
+ details: {
72
+ resetTime: error.resetTime.toISOString(),
73
+ remaining: error.remaining,
74
+ },
75
+ },
76
+ }),
77
+ },
78
+ ],
79
+ };
80
+ }
81
+ static hashToken(token) {
82
+ let hash = 0;
83
+ for (let i = 0; i < token.length; i++) {
84
+ const char = token.charCodeAt(i);
85
+ hash = (hash << 5) - hash + char;
86
+ hash = hash & hash;
87
+ }
88
+ return `token_${Math.abs(hash).toString(16)}`;
89
+ }
90
+ static getSecurityStats() {
91
+ return {
92
+ rateLimitStats: {
93
+ message: 'Rate limiting is active with YNAB API limits (200 requests/hour)',
94
+ },
95
+ requestStats: globalRequestLogger.getStats(),
96
+ };
97
+ }
98
+ static reset() {
99
+ globalRateLimiter.reset();
100
+ globalRequestLogger.clearLogs();
101
+ }
102
+ }
103
+ export function withSecurityWrapper(toolName, operation, schema) {
104
+ return (accessToken) => (params) => (handler) => {
105
+ const context = {
106
+ accessToken,
107
+ toolName,
108
+ operation,
109
+ parameters: params,
110
+ startTime: Date.now(),
111
+ };
112
+ const operationAdapter = async (validatedParams) => {
113
+ return handler(validatedParams);
114
+ };
115
+ return SecurityMiddleware.withSecurity(context, schema, operationAdapter);
116
+ };
117
+ }
@@ -0,0 +1,11 @@
1
+ export declare class ServerKnowledgeStore {
2
+ private knowledge;
3
+ get(cacheKey: string): number | undefined;
4
+ update(cacheKey: string, value: number): void;
5
+ reset(keyPattern?: string): void;
6
+ resetByBudgetId(budgetId: string): void;
7
+ getStats(): {
8
+ entryCount: number;
9
+ entries: Record<string, number>;
10
+ };
11
+ }
@@ -0,0 +1,42 @@
1
+ export class ServerKnowledgeStore {
2
+ constructor() {
3
+ this.knowledge = new Map();
4
+ }
5
+ get(cacheKey) {
6
+ return this.knowledge.get(cacheKey);
7
+ }
8
+ update(cacheKey, value) {
9
+ if (value < 0) {
10
+ throw new Error(`server_knowledge must be non-negative, got: ${value}`);
11
+ }
12
+ this.knowledge.set(cacheKey, value);
13
+ }
14
+ reset(keyPattern) {
15
+ if (keyPattern === undefined || keyPattern === '') {
16
+ this.knowledge.clear();
17
+ return;
18
+ }
19
+ const keysToDelete = [];
20
+ for (const key of this.knowledge.keys()) {
21
+ if (key.includes(keyPattern)) {
22
+ keysToDelete.push(key);
23
+ }
24
+ }
25
+ for (const key of keysToDelete) {
26
+ this.knowledge.delete(key);
27
+ }
28
+ }
29
+ resetByBudgetId(budgetId) {
30
+ this.reset(`:${budgetId}`);
31
+ }
32
+ getStats() {
33
+ const entries = {};
34
+ for (const [key, value] of this.knowledge.entries()) {
35
+ entries[key] = value;
36
+ }
37
+ return {
38
+ entryCount: this.knowledge.size,
39
+ entries,
40
+ };
41
+ }
42
+ }
@@ -0,0 +1,85 @@
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
+ 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
+ export interface ErrorHandlerContract {
6
+ handleError(error: unknown, context: string): CallToolResult;
7
+ createValidationError(message: string, details?: string, suggestions?: string[]): CallToolResult;
8
+ }
9
+ export interface ResponseFormatterContract {
10
+ runWithMinifyOverride<T>(minifyOverride: boolean | undefined, fn: () => T): T;
11
+ }
12
+ export interface ToolRegistryCacheHelpers {
13
+ generateKey?: (...segments: unknown[]) => string;
14
+ invalidate?: (key: string) => void | Promise<void>;
15
+ clear?: () => void | Promise<void>;
16
+ }
17
+ export interface DefaultArgumentResolverContext {
18
+ name: string;
19
+ accessToken: string;
20
+ rawArguments: Record<string, unknown>;
21
+ }
22
+ export declare class DefaultArgumentResolutionError extends Error {
23
+ readonly result: CallToolResult;
24
+ constructor(result: CallToolResult);
25
+ }
26
+ export type DefaultArgumentResolver<TInput extends Record<string, unknown>> = (context: DefaultArgumentResolverContext) => Partial<TInput> | Promise<Partial<TInput> | undefined> | undefined;
27
+ export interface ToolSecurityOptions {
28
+ namespace?: string;
29
+ operation?: string;
30
+ }
31
+ export interface ToolMetadataOptions {
32
+ inputJsonSchema?: Record<string, unknown>;
33
+ annotations?: MCPToolAnnotations;
34
+ }
35
+ export interface ToolExecutionContext {
36
+ accessToken: string;
37
+ name: string;
38
+ operation: string;
39
+ rawArguments: Record<string, unknown>;
40
+ cache?: ToolRegistryCacheHelpers;
41
+ }
42
+ export interface ToolExecutionPayload<TInput extends Record<string, unknown>> {
43
+ input: TInput;
44
+ context: ToolExecutionContext;
45
+ }
46
+ export type ToolHandler<TInput extends Record<string, unknown>> = (payload: ToolExecutionPayload<TInput>) => Promise<CallToolResult>;
47
+ export interface ToolDefinition<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>> {
48
+ name: string;
49
+ description: string;
50
+ inputSchema: z.ZodSchema<TInput>;
51
+ outputSchema?: z.ZodSchema<TOutput>;
52
+ handler: ToolHandler<TInput>;
53
+ security?: ToolSecurityOptions;
54
+ metadata?: ToolMetadataOptions;
55
+ defaultArgumentResolver?: DefaultArgumentResolver<TInput>;
56
+ }
57
+ export interface ToolExecutionOptions {
58
+ name: string;
59
+ accessToken: string;
60
+ arguments?: Record<string, unknown>;
61
+ minifyOverride?: boolean;
62
+ }
63
+ export interface ToolRegistryDependencies {
64
+ withSecurityWrapper: SecurityWrapperFactory;
65
+ errorHandler: ErrorHandlerContract;
66
+ responseFormatter: ResponseFormatterContract;
67
+ cacheHelpers?: ToolRegistryCacheHelpers;
68
+ validateAccessToken?: (token: string) => Promise<void> | void;
69
+ }
70
+ export declare class ToolRegistry {
71
+ private readonly deps;
72
+ private readonly tools;
73
+ private readonly outputValidators;
74
+ constructor(deps: ToolRegistryDependencies);
75
+ register<TInput extends Record<string, unknown>, TOutput extends Record<string, unknown>>(definition: ToolDefinition<TInput, TOutput>): void;
76
+ listTools(): Tool[];
77
+ getToolDefinitions(): ToolDefinition[];
78
+ executeTool(options: ToolExecutionOptions): Promise<CallToolResult>;
79
+ private isCallToolResult;
80
+ private normalizeSecurityError;
81
+ private extractMinifyOverride;
82
+ private assertValidDefinition;
83
+ private generateJsonSchema;
84
+ private validateOutput;
85
+ }
@@ -0,0 +1,272 @@
1
+ import { z, toJSONSchema } from 'zod/v4';
2
+ export class DefaultArgumentResolutionError extends Error {
3
+ constructor(result) {
4
+ super('Default argument resolution failed');
5
+ this.result = result;
6
+ this.name = 'DefaultArgumentResolutionError';
7
+ }
8
+ }
9
+ const MINIFY_HINT_KEYS = ['minify', '_minify', '__minify'];
10
+ export class ToolRegistry {
11
+ constructor(deps) {
12
+ this.deps = deps;
13
+ this.tools = new Map();
14
+ this.outputValidators = new Map();
15
+ }
16
+ register(definition) {
17
+ this.assertValidDefinition(definition);
18
+ if (this.tools.has(definition.name)) {
19
+ throw new Error(`Tool '${definition.name}' is already registered`);
20
+ }
21
+ const resolved = {
22
+ ...definition,
23
+ security: {
24
+ namespace: definition.security?.namespace ?? 'ynab',
25
+ operation: definition.security?.operation ?? definition.name,
26
+ },
27
+ };
28
+ const registeredTool = resolved;
29
+ this.tools.set(definition.name, registeredTool);
30
+ if (definition.outputSchema) {
31
+ this.outputValidators.set(definition.name, definition.outputSchema);
32
+ }
33
+ }
34
+ listTools() {
35
+ return Array.from(this.tools.values()).map((tool) => {
36
+ const inputSchema = tool.metadata?.inputJsonSchema ??
37
+ this.generateJsonSchema(tool.inputSchema);
38
+ const result = {
39
+ name: tool.name,
40
+ description: tool.description,
41
+ inputSchema,
42
+ };
43
+ if (tool.outputSchema) {
44
+ const outputSchema = this.generateJsonSchema(tool.outputSchema);
45
+ result.outputSchema = outputSchema;
46
+ }
47
+ if (tool.metadata?.annotations) {
48
+ result.annotations = tool.metadata.annotations;
49
+ }
50
+ return result;
51
+ });
52
+ }
53
+ getToolDefinitions() {
54
+ return Array.from(this.tools.values()).map((tool) => {
55
+ const definition = {
56
+ name: tool.name,
57
+ description: tool.description,
58
+ inputSchema: tool.inputSchema,
59
+ handler: tool.handler,
60
+ security: tool.security,
61
+ };
62
+ if (tool.outputSchema) {
63
+ definition.outputSchema = tool.outputSchema;
64
+ }
65
+ if (tool.metadata) {
66
+ definition.metadata = tool.metadata;
67
+ }
68
+ if (tool.defaultArgumentResolver) {
69
+ definition.defaultArgumentResolver = tool.defaultArgumentResolver;
70
+ }
71
+ return definition;
72
+ });
73
+ }
74
+ async executeTool(options) {
75
+ const tool = this.tools.get(options.name);
76
+ if (!tool) {
77
+ return this.deps.errorHandler.createValidationError(`Unknown tool: ${options.name}`, 'The requested tool is not registered with the server');
78
+ }
79
+ if (this.deps.validateAccessToken) {
80
+ try {
81
+ await this.deps.validateAccessToken(options.accessToken);
82
+ }
83
+ catch (error) {
84
+ if (this.isCallToolResult(error)) {
85
+ return error;
86
+ }
87
+ return this.deps.errorHandler.handleError(error, `authenticating ${tool.name}`);
88
+ }
89
+ }
90
+ let defaults;
91
+ if (tool.defaultArgumentResolver) {
92
+ try {
93
+ defaults = await tool.defaultArgumentResolver({
94
+ name: tool.name,
95
+ accessToken: options.accessToken,
96
+ rawArguments: options.arguments ?? {},
97
+ });
98
+ }
99
+ catch (error) {
100
+ if (error instanceof DefaultArgumentResolutionError) {
101
+ return error.result;
102
+ }
103
+ if (this.isCallToolResult(error)) {
104
+ return error;
105
+ }
106
+ return this.deps.errorHandler.createValidationError('Invalid parameters', error instanceof Error
107
+ ? error.message
108
+ : 'Unknown error during default argument resolution');
109
+ }
110
+ }
111
+ const rawArguments = {
112
+ ...(defaults ?? {}),
113
+ ...(options.arguments ?? {}),
114
+ };
115
+ const minifyOverride = this.extractMinifyOverride(options, rawArguments);
116
+ const run = async () => {
117
+ try {
118
+ const secured = this.deps.withSecurityWrapper(tool.security.namespace, tool.security.operation, tool.inputSchema)(options.accessToken)(rawArguments);
119
+ return await secured(async (validated) => {
120
+ try {
121
+ const context = {
122
+ accessToken: options.accessToken,
123
+ name: tool.name,
124
+ operation: tool.security.operation,
125
+ rawArguments,
126
+ };
127
+ if (this.deps.cacheHelpers) {
128
+ context.cache = this.deps.cacheHelpers;
129
+ }
130
+ const handlerResult = await tool.handler({
131
+ input: validated,
132
+ context,
133
+ });
134
+ if (handlerResult.isError) {
135
+ return handlerResult;
136
+ }
137
+ return this.validateOutput(tool.name, handlerResult);
138
+ }
139
+ catch (handlerError) {
140
+ return this.deps.errorHandler.handleError(handlerError, `executing ${tool.name} - ${tool.security.operation}`);
141
+ }
142
+ });
143
+ }
144
+ catch (securityError) {
145
+ return this.normalizeSecurityError(securityError, tool);
146
+ }
147
+ };
148
+ try {
149
+ return await this.deps.responseFormatter.runWithMinifyOverride(minifyOverride, run);
150
+ }
151
+ catch (formatterError) {
152
+ return this.deps.errorHandler.handleError(formatterError, `formatting response for ${tool.name}`);
153
+ }
154
+ }
155
+ isCallToolResult(value) {
156
+ return (typeof value === 'object' &&
157
+ value !== null &&
158
+ 'content' in value &&
159
+ Array.isArray(value.content));
160
+ }
161
+ normalizeSecurityError(error, tool) {
162
+ if (error instanceof z.ZodError) {
163
+ return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, error.message);
164
+ }
165
+ if (error instanceof Error && error.message.includes('Validation failed')) {
166
+ return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, error.message);
167
+ }
168
+ return this.deps.errorHandler.handleError(error, `executing ${tool.name}`);
169
+ }
170
+ extractMinifyOverride(options, args) {
171
+ if (typeof options.minifyOverride === 'boolean') {
172
+ return options.minifyOverride;
173
+ }
174
+ for (const key of MINIFY_HINT_KEYS) {
175
+ const value = args[key];
176
+ if (typeof value === 'boolean') {
177
+ delete args[key];
178
+ return value;
179
+ }
180
+ }
181
+ return undefined;
182
+ }
183
+ assertValidDefinition(definition) {
184
+ if (!definition || typeof definition !== 'object') {
185
+ throw new Error('Tool definition must be an object');
186
+ }
187
+ if (!definition.name || typeof definition.name !== 'string') {
188
+ throw new Error('Tool definition requires a non-empty name');
189
+ }
190
+ if (!definition.description || typeof definition.description !== 'string') {
191
+ throw new Error(`Tool '${definition.name}' requires a description`);
192
+ }
193
+ if (!definition.inputSchema || typeof definition.inputSchema.parse !== 'function') {
194
+ throw new Error(`Tool '${definition.name}' requires a valid Zod schema`);
195
+ }
196
+ if (definition.outputSchema && typeof definition.outputSchema.parse !== 'function') {
197
+ throw new Error(`Tool '${definition.name}' outputSchema must be a valid Zod schema when provided`);
198
+ }
199
+ if (typeof definition.handler !== 'function') {
200
+ throw new Error(`Tool '${definition.name}' requires a handler function`);
201
+ }
202
+ if (definition.defaultArgumentResolver &&
203
+ typeof definition.defaultArgumentResolver !== 'function') {
204
+ throw new Error(`Tool '${definition.name}' defaultArgumentResolver must be a function when provided`);
205
+ }
206
+ }
207
+ generateJsonSchema(schema) {
208
+ try {
209
+ return toJSONSchema(schema, { target: 'draft-2020-12', io: 'output' });
210
+ }
211
+ catch (error) {
212
+ console.warn(`Failed to generate JSON schema for tool: ${error}`);
213
+ return { type: 'object', additionalProperties: true };
214
+ }
215
+ }
216
+ validateOutput(toolName, output) {
217
+ const validator = this.outputValidators.get(toolName);
218
+ if (!validator) {
219
+ return output;
220
+ }
221
+ if (!output.content || output.content.length === 0) {
222
+ return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, 'Handler returned empty content', ['Ensure the handler returns valid content in the response']);
223
+ }
224
+ const invalidItems = [];
225
+ for (let i = 0; i < output.content.length; i++) {
226
+ const item = output.content[i];
227
+ if (!item) {
228
+ invalidItems.push({ index: i, reason: 'item is null or undefined' });
229
+ }
230
+ else if (item.type !== 'text') {
231
+ invalidItems.push({ index: i, reason: `type is "${item.type}" instead of "text"` });
232
+ }
233
+ else if (typeof item.text !== 'string') {
234
+ invalidItems.push({
235
+ index: i,
236
+ reason: `text property is ${typeof item.text} instead of string`,
237
+ });
238
+ }
239
+ }
240
+ if (invalidItems.length > 0) {
241
+ const invalidItemsDetails = invalidItems
242
+ .map((inv) => ` - Item ${inv.index}: ${inv.reason}`)
243
+ .join('\n');
244
+ 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']);
245
+ }
246
+ const firstContent = output.content[0];
247
+ if (firstContent.type !== 'text') {
248
+ throw new Error('Unexpected: firstContent is not text after validation');
249
+ }
250
+ let parsedOutput;
251
+ try {
252
+ parsedOutput = JSON.parse(firstContent.text);
253
+ }
254
+ catch (parseError) {
255
+ 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']);
256
+ }
257
+ const result = validator.safeParse(parsedOutput);
258
+ if (!result.success) {
259
+ const validationErrors = result.error.issues
260
+ .map((err) => {
261
+ const path = err.path.join('.');
262
+ return path ? `${path}: ${err.message}` : err.message;
263
+ })
264
+ .join('; ');
265
+ return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Handler output does not match declared output schema: ${validationErrors}`, [
266
+ 'Check that the handler returns data matching the output schema',
267
+ 'Review the tool definition output schema',
268
+ ]);
269
+ }
270
+ return output;
271
+ }
272
+ }