@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.
- package/.chunkhound.json +11 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
- package/.dxtignore +57 -0
- package/.env.example +44 -0
- package/.gemini/settings.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
- package/.github/pull_request_template.md +41 -0
- package/.github/workflows/ci-tests.yml +41 -0
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/full-integration.yml +22 -0
- package/.github/workflows/pr-description-check.yml +88 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/release.yml +89 -0
- package/.mcpbignore +58 -0
- package/.prettierignore +10 -0
- package/.prettierrc.json +10 -0
- package/ADOS-2-Module-1-Complete-Manual.md +757 -0
- package/AGENTS.md +36 -0
- package/CHANGELOG.md +187 -0
- package/CLAUDE.md +414 -0
- package/CODEREVIEW_RESPONSE.md +128 -0
- package/LICENSE +17 -0
- package/NUL +1 -0
- package/README.md +222 -0
- package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
- package/TESTING_NOTES.md +217 -0
- package/WARP.md +245 -0
- package/accountactivity-merged.csv +149 -0
- package/bin/ynab-mcp-server.cjs +4 -0
- package/bin/ynab-mcp-server.js +8 -0
- package/bundle-analysis.html +13110 -0
- package/dist/bundle/index.cjs +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/server/YNABMCPServer.d.ts +264 -0
- package/dist/server/YNABMCPServer.js +845 -0
- package/dist/server/budgetResolver.d.ts +15 -0
- package/dist/server/budgetResolver.js +99 -0
- package/dist/server/cacheManager.d.ts +74 -0
- package/dist/server/cacheManager.js +306 -0
- package/dist/server/config.d.ts +3 -0
- package/dist/server/config.js +19 -0
- package/dist/server/deltaCache.d.ts +61 -0
- package/dist/server/deltaCache.js +206 -0
- package/dist/server/deltaCache.merge.d.ts +9 -0
- package/dist/server/deltaCache.merge.js +111 -0
- package/dist/server/diagnostics.d.ts +90 -0
- package/dist/server/diagnostics.js +163 -0
- package/dist/server/errorHandler.d.ts +69 -0
- package/dist/server/errorHandler.js +524 -0
- package/dist/server/prompts.d.ts +31 -0
- package/dist/server/prompts.js +205 -0
- package/dist/server/rateLimiter.d.ts +27 -0
- package/dist/server/rateLimiter.js +82 -0
- package/dist/server/requestLogger.d.ts +62 -0
- package/dist/server/requestLogger.js +190 -0
- package/dist/server/resources.d.ts +39 -0
- package/dist/server/resources.js +85 -0
- package/dist/server/responseFormatter.d.ts +14 -0
- package/dist/server/responseFormatter.js +42 -0
- package/dist/server/securityMiddleware.d.ts +87 -0
- package/dist/server/securityMiddleware.js +117 -0
- package/dist/server/serverKnowledgeStore.d.ts +11 -0
- package/dist/server/serverKnowledgeStore.js +42 -0
- package/dist/server/toolRegistry.d.ts +85 -0
- package/dist/server/toolRegistry.js +272 -0
- package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
- package/dist/tools/__tests__/deltaTestUtils.js +26 -0
- package/dist/tools/accountTools.d.ts +37 -0
- package/dist/tools/accountTools.js +175 -0
- package/dist/tools/budgetTools.d.ts +10 -0
- package/dist/tools/budgetTools.js +68 -0
- package/dist/tools/categoryTools.d.ts +27 -0
- package/dist/tools/categoryTools.js +232 -0
- package/dist/tools/compareTransactions/formatter.d.ts +71 -0
- package/dist/tools/compareTransactions/formatter.js +97 -0
- package/dist/tools/compareTransactions/index.d.ts +30 -0
- package/dist/tools/compareTransactions/index.js +160 -0
- package/dist/tools/compareTransactions/matcher.d.ts +12 -0
- package/dist/tools/compareTransactions/matcher.js +140 -0
- package/dist/tools/compareTransactions/parser.d.ts +14 -0
- package/dist/tools/compareTransactions/parser.js +430 -0
- package/dist/tools/compareTransactions/types.d.ts +27 -0
- package/dist/tools/compareTransactions/types.js +1 -0
- package/dist/tools/compareTransactions.d.ts +1 -0
- package/dist/tools/compareTransactions.js +1 -0
- package/dist/tools/deltaFetcher.d.ts +22 -0
- package/dist/tools/deltaFetcher.js +137 -0
- package/dist/tools/deltaSupport.d.ts +20 -0
- package/dist/tools/deltaSupport.js +176 -0
- package/dist/tools/exportTransactions.d.ts +17 -0
- package/dist/tools/exportTransactions.js +191 -0
- package/dist/tools/monthTools.d.ts +16 -0
- package/dist/tools/monthTools.js +107 -0
- package/dist/tools/payeeTools.d.ts +17 -0
- package/dist/tools/payeeTools.js +82 -0
- package/dist/tools/reconcileAdapter.d.ts +25 -0
- package/dist/tools/reconcileAdapter.js +167 -0
- package/dist/tools/reconciliation/analyzer.d.ts +3 -0
- package/dist/tools/reconciliation/analyzer.js +567 -0
- package/dist/tools/reconciliation/executor.d.ts +94 -0
- package/dist/tools/reconciliation/executor.js +611 -0
- package/dist/tools/reconciliation/index.d.ts +54 -0
- package/dist/tools/reconciliation/index.js +249 -0
- package/dist/tools/reconciliation/matcher.d.ts +3 -0
- package/dist/tools/reconciliation/matcher.js +160 -0
- package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
- package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
- package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
- package/dist/tools/reconciliation/recommendationEngine.js +273 -0
- package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
- package/dist/tools/reconciliation/reportFormatter.js +214 -0
- package/dist/tools/reconciliation/types.d.ts +172 -0
- package/dist/tools/reconciliation/types.js +7 -0
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
- package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
- package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
- package/dist/tools/schemas/outputs/index.d.ts +14 -0
- package/dist/tools/schemas/outputs/index.js +14 -0
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
- package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
- package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
- package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
- package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
- package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
- package/dist/tools/schemas/shared/commonOutputs.js +27 -0
- package/dist/tools/toolCategories.d.ts +32 -0
- package/dist/tools/toolCategories.js +32 -0
- package/dist/tools/transactionTools.d.ts +315 -0
- package/dist/tools/transactionTools.js +1722 -0
- package/dist/tools/utilityTools.d.ts +10 -0
- package/dist/tools/utilityTools.js +56 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.js +16 -0
- package/dist/types/toolAnnotations.d.ts +7 -0
- package/dist/types/toolAnnotations.js +1 -0
- package/dist/utils/amountUtils.d.ts +3 -0
- package/dist/utils/amountUtils.js +10 -0
- package/dist/utils/dateUtils.d.ts +9 -0
- package/dist/utils/dateUtils.js +43 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +51 -0
- package/docs/README.md +72 -0
- package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
- package/docs/assets/schemas/reconciliation-v2.json +338 -0
- package/docs/getting-started/CONFIGURATION.md +175 -0
- package/docs/getting-started/INSTALLATION.md +333 -0
- package/docs/getting-started/QUICKSTART.md +282 -0
- package/docs/guides/ARCHITECTURE.md +650 -0
- package/docs/guides/DEPLOYMENT.md +189 -0
- package/docs/guides/INTEGRATION_TESTING.md +730 -0
- package/docs/guides/TESTING.md +591 -0
- package/docs/reconciliation-flow.md +83 -0
- package/docs/reference/API.md +1450 -0
- package/docs/reference/EXAMPLES.md +946 -0
- package/docs/reference/TOOLS.md +348 -0
- package/docs/reference/TROUBLESHOOTING.md +481 -0
- package/esbuild.config.mjs +68 -0
- package/eslint.config.js +49 -0
- package/fix-types.sh +17 -0
- package/meta.json +12550 -0
- package/package.json +105 -0
- package/package.json.tmp +105 -0
- package/scripts/analyze-bundle.mjs +41 -0
- package/scripts/create-pr-description.js +203 -0
- package/scripts/generate-mcpb.ps1 +96 -0
- package/scripts/run-domain-integration-tests.js +33 -0
- package/scripts/run-generate-mcpb.js +29 -0
- package/scripts/run-throttled-integration-tests.js +116 -0
- package/scripts/test-delta-params.mjs +140 -0
- package/scripts/test-recommendations.ts +53 -0
- package/scripts/tmpTransaction.ts +48 -0
- package/scripts/validate-env.js +122 -0
- package/scripts/verify-build.js +105 -0
- package/scripts/watch-and-restart.ps1 +50 -0
- package/src/__tests__/comprehensive.integration.test.ts +1196 -0
- package/src/__tests__/delta.performance.test.ts +80 -0
- package/src/__tests__/performance.test.ts +725 -0
- package/src/__tests__/setup.ts +449 -0
- package/src/__tests__/testRunner.ts +444 -0
- package/src/__tests__/testUtils.ts +563 -0
- package/src/__tests__/workflows.e2e.test.ts +1675 -0
- package/src/index.ts +124 -0
- package/src/server/.gitkeep +1 -0
- package/src/server/YNABMCPServer.ts +1188 -0
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
- package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
- package/src/server/__tests__/budgetResolver.test.ts +425 -0
- package/src/server/__tests__/cacheManager.test.ts +880 -0
- package/src/server/__tests__/config.test.ts +166 -0
- package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
- package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
- package/src/server/__tests__/deltaCache.test.ts +774 -0
- package/src/server/__tests__/diagnostics.test.ts +823 -0
- package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
- package/src/server/__tests__/errorHandler.test.ts +416 -0
- package/src/server/__tests__/prompts.test.ts +354 -0
- package/src/server/__tests__/rateLimiter.test.ts +314 -0
- package/src/server/__tests__/requestLogger.test.ts +408 -0
- package/src/server/__tests__/resources.test.ts +299 -0
- package/src/server/__tests__/security.integration.test.ts +426 -0
- package/src/server/__tests__/securityMiddleware.test.ts +449 -0
- package/src/server/__tests__/server-startup.integration.test.ts +477 -0
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
- package/src/server/__tests__/toolRegistry.test.ts +855 -0
- package/src/server/budgetResolver.ts +235 -0
- package/src/server/cacheManager.ts +503 -0
- package/src/server/config.ts +41 -0
- package/src/server/deltaCache.merge.ts +149 -0
- package/src/server/deltaCache.ts +341 -0
- package/src/server/diagnostics.ts +338 -0
- package/src/server/errorHandler.ts +756 -0
- package/src/server/prompts.ts +291 -0
- package/src/server/rateLimiter.ts +156 -0
- package/src/server/requestLogger.ts +344 -0
- package/src/server/resources.ts +168 -0
- package/src/server/responseFormatter.ts +51 -0
- package/src/server/securityMiddleware.ts +236 -0
- package/src/server/serverKnowledgeStore.ts +91 -0
- package/src/server/toolRegistry.ts +489 -0
- package/src/tools/.gitkeep +1 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
- package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
- package/src/tools/__tests__/accountTools.test.ts +653 -0
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
- package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
- package/src/tools/__tests__/budgetTools.test.ts +423 -0
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
- package/src/tools/__tests__/categoryTools.test.ts +622 -0
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
- package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
- package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
- package/src/tools/__tests__/compareTransactions.test.ts +342 -0
- package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
- package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
- package/src/tools/__tests__/deltaSupport.test.ts +188 -0
- package/src/tools/__tests__/deltaTestUtils.ts +46 -0
- package/src/tools/__tests__/exportTransactions.test.ts +213 -0
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
- package/src/tools/__tests__/monthTools.test.ts +523 -0
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
- package/src/tools/__tests__/payeeTools.test.ts +445 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
- package/src/tools/__tests__/transactionTools.test.ts +3521 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
- package/src/tools/__tests__/utilityTools.test.ts +205 -0
- package/src/tools/accountTools.ts +283 -0
- package/src/tools/budgetTools.ts +112 -0
- package/src/tools/categoryTools.ts +366 -0
- package/src/tools/compareTransactions/formatter.ts +163 -0
- package/src/tools/compareTransactions/index.ts +228 -0
- package/src/tools/compareTransactions/matcher.ts +240 -0
- package/src/tools/compareTransactions/parser.ts +557 -0
- package/src/tools/compareTransactions/types.ts +60 -0
- package/src/tools/compareTransactions.ts +3 -0
- package/src/tools/deltaFetcher.ts +278 -0
- package/src/tools/deltaSupport.ts +293 -0
- package/src/tools/exportTransactions.ts +273 -0
- package/src/tools/monthTools.ts +164 -0
- package/src/tools/payeeTools.ts +140 -0
- package/src/tools/reconcileAdapter.ts +312 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
- package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
- package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
- package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
- package/src/tools/reconciliation/analyzer.ts +824 -0
- package/src/tools/reconciliation/executor.ts +880 -0
- package/src/tools/reconciliation/index.ts +400 -0
- package/src/tools/reconciliation/matcher.ts +269 -0
- package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
- package/src/tools/reconciliation/recommendationEngine.ts +506 -0
- package/src/tools/reconciliation/reportFormatter.ts +363 -0
- package/src/tools/reconciliation/types.ts +314 -0
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
- package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
- package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
- package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
- package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
- package/src/tools/schemas/outputs/index.ts +270 -0
- package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
- package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
- package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
- package/src/tools/schemas/shared/commonOutputs.ts +140 -0
- package/src/tools/toolCategories.ts +140 -0
- package/src/tools/transactionTools.ts +2509 -0
- package/src/tools/utilityTools.ts +90 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/__tests__/index.test.ts +52 -0
- package/src/types/index.ts +67 -0
- package/src/types/integration-tests.d.ts +35 -0
- package/src/types/toolAnnotations.ts +44 -0
- package/src/utils/__tests__/dateUtils.test.ts +170 -0
- package/src/utils/__tests__/money.test.ts +189 -0
- package/src/utils/amountUtils.ts +32 -0
- package/src/utils/dateUtils.ts +108 -0
- package/src/utils/money.ts +123 -0
- package/test-csv-sample.csv +28 -0
- package/test-exports/sample_bank_statement.csv +7 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
- package/test-reconcile-autodetect.js +40 -0
- package/test-reconcile-tool.js +152 -0
- package/test-reconcile-with-csv.cjs +89 -0
- package/test-statement.csv +8 -0
- package/test_debug.js +47 -0
- package/test_simple.mjs +16 -0
- package/tsconfig.json +31 -0
- package/tsconfig.prod.json +18 -0
- package/vitest-reporters/split-json-reporter.ts +211 -0
- package/vitest.config.ts +96 -0
|
@@ -0,0 +1,1188 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema,
|
|
9
|
+
ListResourcesRequestSchema,
|
|
10
|
+
ListPromptsRequestSchema,
|
|
11
|
+
ReadResourceRequestSchema,
|
|
12
|
+
GetPromptRequestSchema,
|
|
13
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import * as ynab from 'ynab';
|
|
16
|
+
import {
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
ConfigurationError,
|
|
19
|
+
ServerConfig,
|
|
20
|
+
ErrorHandler,
|
|
21
|
+
YNABErrorCode,
|
|
22
|
+
ValidationError,
|
|
23
|
+
} from '../types/index.js';
|
|
24
|
+
import { createErrorHandler } from './errorHandler.js';
|
|
25
|
+
import { BudgetResolver } from './budgetResolver.js';
|
|
26
|
+
import { SecurityMiddleware, withSecurityWrapper } from './securityMiddleware.js';
|
|
27
|
+
import { handleListBudgets, handleGetBudget, GetBudgetSchema } from '../tools/budgetTools.js';
|
|
28
|
+
import {
|
|
29
|
+
handleListAccounts,
|
|
30
|
+
handleGetAccount,
|
|
31
|
+
handleCreateAccount,
|
|
32
|
+
ListAccountsSchema,
|
|
33
|
+
GetAccountSchema,
|
|
34
|
+
CreateAccountSchema,
|
|
35
|
+
} from '../tools/accountTools.js';
|
|
36
|
+
import {
|
|
37
|
+
handleListTransactions,
|
|
38
|
+
handleGetTransaction,
|
|
39
|
+
handleCreateTransaction,
|
|
40
|
+
handleCreateTransactions,
|
|
41
|
+
handleCreateReceiptSplitTransaction,
|
|
42
|
+
handleUpdateTransaction,
|
|
43
|
+
handleUpdateTransactions,
|
|
44
|
+
handleDeleteTransaction,
|
|
45
|
+
ListTransactionsSchema,
|
|
46
|
+
GetTransactionSchema,
|
|
47
|
+
CreateTransactionSchema,
|
|
48
|
+
CreateTransactionsSchema,
|
|
49
|
+
CreateReceiptSplitTransactionSchema,
|
|
50
|
+
UpdateTransactionSchema,
|
|
51
|
+
UpdateTransactionsSchema,
|
|
52
|
+
DeleteTransactionSchema,
|
|
53
|
+
} from '../tools/transactionTools.js';
|
|
54
|
+
import { handleExportTransactions, ExportTransactionsSchema } from '../tools/exportTransactions.js';
|
|
55
|
+
import {
|
|
56
|
+
handleCompareTransactions,
|
|
57
|
+
CompareTransactionsSchema,
|
|
58
|
+
} from '../tools/compareTransactions/index.js';
|
|
59
|
+
import { handleReconcileAccount, ReconcileAccountSchema } from '../tools/reconciliation/index.js';
|
|
60
|
+
import {
|
|
61
|
+
handleListCategories,
|
|
62
|
+
handleGetCategory,
|
|
63
|
+
handleUpdateCategory,
|
|
64
|
+
ListCategoriesSchema,
|
|
65
|
+
GetCategorySchema,
|
|
66
|
+
UpdateCategorySchema,
|
|
67
|
+
} from '../tools/categoryTools.js';
|
|
68
|
+
import {
|
|
69
|
+
handleListPayees,
|
|
70
|
+
handleGetPayee,
|
|
71
|
+
ListPayeesSchema,
|
|
72
|
+
GetPayeeSchema,
|
|
73
|
+
} from '../tools/payeeTools.js';
|
|
74
|
+
import {
|
|
75
|
+
handleGetMonth,
|
|
76
|
+
handleListMonths,
|
|
77
|
+
GetMonthSchema,
|
|
78
|
+
ListMonthsSchema,
|
|
79
|
+
} from '../tools/monthTools.js';
|
|
80
|
+
import { handleGetUser, handleConvertAmount, ConvertAmountSchema } from '../tools/utilityTools.js';
|
|
81
|
+
import { cacheManager, CacheManager } from './cacheManager.js';
|
|
82
|
+
import { responseFormatter } from './responseFormatter.js';
|
|
83
|
+
import {
|
|
84
|
+
ToolRegistry,
|
|
85
|
+
DefaultArgumentResolutionError,
|
|
86
|
+
type ToolDefinition,
|
|
87
|
+
type DefaultArgumentResolver,
|
|
88
|
+
type ToolExecutionPayload,
|
|
89
|
+
} from './toolRegistry.js';
|
|
90
|
+
import { validateEnvironment } from './config.js';
|
|
91
|
+
import { ResourceManager } from './resources.js';
|
|
92
|
+
import { PromptManager } from './prompts.js';
|
|
93
|
+
import { DiagnosticManager } from './diagnostics.js';
|
|
94
|
+
import { ServerKnowledgeStore } from './serverKnowledgeStore.js';
|
|
95
|
+
import { DeltaCache } from './deltaCache.js';
|
|
96
|
+
import { DeltaFetcher } from '../tools/deltaFetcher.js';
|
|
97
|
+
import { ToolAnnotationPresets } from '../tools/toolCategories.js';
|
|
98
|
+
import {
|
|
99
|
+
GetUserOutputSchema,
|
|
100
|
+
ConvertAmountOutputSchema,
|
|
101
|
+
GetDefaultBudgetOutputSchema,
|
|
102
|
+
SetDefaultBudgetOutputSchema,
|
|
103
|
+
ClearCacheOutputSchema,
|
|
104
|
+
SetOutputFormatOutputSchema,
|
|
105
|
+
DiagnosticInfoOutputSchema,
|
|
106
|
+
GetBudgetOutputSchema,
|
|
107
|
+
ListBudgetsOutputSchema,
|
|
108
|
+
ListAccountsOutputSchema,
|
|
109
|
+
GetAccountOutputSchema,
|
|
110
|
+
CreateAccountOutputSchema,
|
|
111
|
+
ListTransactionsOutputSchema,
|
|
112
|
+
GetTransactionOutputSchema,
|
|
113
|
+
ExportTransactionsOutputSchema,
|
|
114
|
+
CompareTransactionsOutputSchema,
|
|
115
|
+
CreateTransactionOutputSchema,
|
|
116
|
+
CreateTransactionsOutputSchema,
|
|
117
|
+
UpdateTransactionOutputSchema,
|
|
118
|
+
UpdateTransactionsOutputSchema,
|
|
119
|
+
DeleteTransactionOutputSchema,
|
|
120
|
+
CreateReceiptSplitTransactionOutputSchema,
|
|
121
|
+
ListCategoriesOutputSchema,
|
|
122
|
+
GetCategoryOutputSchema,
|
|
123
|
+
UpdateCategoryOutputSchema,
|
|
124
|
+
ListPayeesOutputSchema,
|
|
125
|
+
GetPayeeOutputSchema,
|
|
126
|
+
GetMonthOutputSchema,
|
|
127
|
+
ListMonthsOutputSchema,
|
|
128
|
+
ReconcileAccountOutputSchema,
|
|
129
|
+
} from '../tools/schemas/outputs/index.js';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* YNAB MCP Server class that provides integration with You Need A Budget API
|
|
133
|
+
*/
|
|
134
|
+
export class YNABMCPServer {
|
|
135
|
+
private server: Server;
|
|
136
|
+
private ynabAPI: ynab.API;
|
|
137
|
+
private config: ServerConfig;
|
|
138
|
+
private exitOnError: boolean;
|
|
139
|
+
private defaultBudgetId: string | undefined;
|
|
140
|
+
private serverVersion: string;
|
|
141
|
+
private toolRegistry: ToolRegistry;
|
|
142
|
+
private resourceManager: ResourceManager;
|
|
143
|
+
private promptManager: PromptManager;
|
|
144
|
+
private serverKnowledgeStore: ServerKnowledgeStore;
|
|
145
|
+
private deltaCache: DeltaCache;
|
|
146
|
+
private deltaFetcher: DeltaFetcher;
|
|
147
|
+
private diagnosticManager: DiagnosticManager;
|
|
148
|
+
private errorHandler: ErrorHandler;
|
|
149
|
+
|
|
150
|
+
constructor(exitOnError: boolean = true) {
|
|
151
|
+
this.exitOnError = exitOnError;
|
|
152
|
+
// Validate environment variables
|
|
153
|
+
this.config = validateEnvironment();
|
|
154
|
+
if (this.config.defaultBudgetId !== undefined) {
|
|
155
|
+
this.defaultBudgetId = this.config.defaultBudgetId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Initialize YNAB API
|
|
159
|
+
this.ynabAPI = new ynab.API(this.config.accessToken);
|
|
160
|
+
|
|
161
|
+
// Determine server version (prefer package.json)
|
|
162
|
+
this.serverVersion = this.readPackageVersion() ?? '0.0.0';
|
|
163
|
+
|
|
164
|
+
// Initialize MCP Server
|
|
165
|
+
this.server = new Server(
|
|
166
|
+
{
|
|
167
|
+
name: 'ynab-mcp-server',
|
|
168
|
+
version: this.serverVersion,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
capabilities: {
|
|
172
|
+
tools: {},
|
|
173
|
+
resources: {},
|
|
174
|
+
prompts: {},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Create ErrorHandler instance with formatter injection
|
|
180
|
+
this.errorHandler = createErrorHandler(responseFormatter);
|
|
181
|
+
|
|
182
|
+
// Set the global default for backward compatibility with static usage
|
|
183
|
+
ErrorHandler.setFormatter(responseFormatter);
|
|
184
|
+
|
|
185
|
+
this.toolRegistry = new ToolRegistry({
|
|
186
|
+
withSecurityWrapper,
|
|
187
|
+
errorHandler: this.errorHandler,
|
|
188
|
+
responseFormatter,
|
|
189
|
+
cacheHelpers: {
|
|
190
|
+
generateKey: (...segments: unknown[]) => {
|
|
191
|
+
const normalized = segments.map((segment) => {
|
|
192
|
+
if (
|
|
193
|
+
typeof segment === 'string' ||
|
|
194
|
+
typeof segment === 'number' ||
|
|
195
|
+
typeof segment === 'boolean' ||
|
|
196
|
+
segment === undefined
|
|
197
|
+
) {
|
|
198
|
+
return segment;
|
|
199
|
+
}
|
|
200
|
+
return JSON.stringify(segment);
|
|
201
|
+
}) as (string | number | boolean | undefined)[];
|
|
202
|
+
return CacheManager.generateKey('tool', ...normalized);
|
|
203
|
+
},
|
|
204
|
+
invalidate: (key: string) => {
|
|
205
|
+
try {
|
|
206
|
+
cacheManager.delete(key);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`Failed to invalidate cache key "${key}":`, error);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
clear: () => {
|
|
212
|
+
try {
|
|
213
|
+
cacheManager.clear();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('Failed to clear cache:', error);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
validateAccessToken: (token: string) => {
|
|
220
|
+
const expected = this.config.accessToken.trim();
|
|
221
|
+
const provided = typeof token === 'string' ? token.trim() : '';
|
|
222
|
+
if (!provided) {
|
|
223
|
+
throw this.errorHandler.createYNABError(
|
|
224
|
+
YNABErrorCode.UNAUTHORIZED,
|
|
225
|
+
'validating access token',
|
|
226
|
+
new Error('Missing access token'),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (provided !== expected) {
|
|
230
|
+
throw this.errorHandler.createYNABError(
|
|
231
|
+
YNABErrorCode.UNAUTHORIZED,
|
|
232
|
+
'validating access token',
|
|
233
|
+
new Error('Access token mismatch'),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Initialize service modules
|
|
240
|
+
this.resourceManager = new ResourceManager({
|
|
241
|
+
ynabAPI: this.ynabAPI,
|
|
242
|
+
responseFormatter,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.promptManager = new PromptManager();
|
|
246
|
+
|
|
247
|
+
this.serverKnowledgeStore = new ServerKnowledgeStore();
|
|
248
|
+
this.deltaCache = new DeltaCache(cacheManager, this.serverKnowledgeStore);
|
|
249
|
+
this.deltaFetcher = new DeltaFetcher(this.ynabAPI, this.deltaCache);
|
|
250
|
+
|
|
251
|
+
this.diagnosticManager = new DiagnosticManager({
|
|
252
|
+
securityMiddleware: SecurityMiddleware,
|
|
253
|
+
cacheManager,
|
|
254
|
+
responseFormatter,
|
|
255
|
+
serverVersion: this.serverVersion,
|
|
256
|
+
serverKnowledgeStore: this.serverKnowledgeStore,
|
|
257
|
+
deltaCache: this.deltaCache,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
this.setupToolRegistry();
|
|
261
|
+
this.setupHandlers();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validates the YNAB access token by making a test API call
|
|
266
|
+
*/
|
|
267
|
+
async validateToken(): Promise<boolean> {
|
|
268
|
+
try {
|
|
269
|
+
await this.ynabAPI.user.getUser();
|
|
270
|
+
return true;
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof Error) {
|
|
273
|
+
// Check for authentication-related errors
|
|
274
|
+
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
275
|
+
throw new AuthenticationError('Invalid or expired YNAB access token');
|
|
276
|
+
}
|
|
277
|
+
if (error.message.includes('403') || error.message.includes('Forbidden')) {
|
|
278
|
+
throw new AuthenticationError('YNAB access token has insufficient permissions');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
throw new AuthenticationError(`Token validation failed: ${error}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Sets up MCP server request handlers
|
|
287
|
+
*/
|
|
288
|
+
private setupHandlers(): void {
|
|
289
|
+
// Handle list resources requests
|
|
290
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
291
|
+
return this.resourceManager.listResources();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Handle read resource requests
|
|
295
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
296
|
+
const { uri } = request.params;
|
|
297
|
+
try {
|
|
298
|
+
return await this.resourceManager.readResource(uri);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return this.errorHandler.handleError(error, `reading resource: ${uri}`);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Handle list prompts requests
|
|
305
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
306
|
+
return this.promptManager.listPrompts();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Handle get prompt requests
|
|
310
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
311
|
+
const { name, arguments: args } = request.params;
|
|
312
|
+
const result = await this.promptManager.getPrompt(name, args);
|
|
313
|
+
// The SDK expects the result to match the protocol's PromptResponse shape
|
|
314
|
+
return result as unknown as { description?: string; messages: unknown[] };
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Handle list tools requests
|
|
318
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
319
|
+
return {
|
|
320
|
+
tools: this.toolRegistry.listTools(),
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Handle tool call requests
|
|
325
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
326
|
+
const rawArgs = (request.params.arguments ?? undefined) as
|
|
327
|
+
| Record<string, unknown>
|
|
328
|
+
| undefined;
|
|
329
|
+
const minifyOverride = this.extractMinifyOverride(rawArgs);
|
|
330
|
+
|
|
331
|
+
const sanitizedArgs = rawArgs
|
|
332
|
+
? (() => {
|
|
333
|
+
const clone: Record<string, unknown> = { ...rawArgs };
|
|
334
|
+
delete clone['minify'];
|
|
335
|
+
delete clone['_minify'];
|
|
336
|
+
delete clone['__minify'];
|
|
337
|
+
return clone;
|
|
338
|
+
})()
|
|
339
|
+
: undefined;
|
|
340
|
+
|
|
341
|
+
const executionOptions: {
|
|
342
|
+
name: string;
|
|
343
|
+
accessToken: string;
|
|
344
|
+
arguments: Record<string, unknown>;
|
|
345
|
+
minifyOverride?: boolean;
|
|
346
|
+
} = {
|
|
347
|
+
name: request.params.name,
|
|
348
|
+
accessToken: this.config.accessToken,
|
|
349
|
+
arguments: sanitizedArgs ?? {},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
if (minifyOverride !== undefined) {
|
|
353
|
+
executionOptions.minifyOverride = minifyOverride;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return await this.toolRegistry.executeTool(executionOptions);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Registers all tools with the registry to centralize handler execution
|
|
362
|
+
*/
|
|
363
|
+
private setupToolRegistry(): void {
|
|
364
|
+
const register = <TInput extends Record<string, unknown>>(
|
|
365
|
+
definition: ToolDefinition<TInput>,
|
|
366
|
+
): void => {
|
|
367
|
+
this.toolRegistry.register(definition);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const adapt =
|
|
371
|
+
<TInput extends Record<string, unknown>>(
|
|
372
|
+
handler: (ynabAPI: ynab.API, params: TInput) => Promise<CallToolResult>,
|
|
373
|
+
) =>
|
|
374
|
+
async ({ input }: ToolExecutionPayload<TInput>): Promise<CallToolResult> =>
|
|
375
|
+
handler(this.ynabAPI, input);
|
|
376
|
+
|
|
377
|
+
const adaptNoInput =
|
|
378
|
+
(handler: (ynabAPI: ynab.API) => Promise<CallToolResult>) =>
|
|
379
|
+
async (_payload: ToolExecutionPayload<Record<string, unknown>>): Promise<CallToolResult> =>
|
|
380
|
+
handler(this.ynabAPI);
|
|
381
|
+
|
|
382
|
+
const adaptWithDelta =
|
|
383
|
+
<TInput extends Record<string, unknown>>(
|
|
384
|
+
handler: (
|
|
385
|
+
ynabAPI: ynab.API,
|
|
386
|
+
deltaFetcher: DeltaFetcher,
|
|
387
|
+
params: TInput,
|
|
388
|
+
) => Promise<CallToolResult>,
|
|
389
|
+
) =>
|
|
390
|
+
async ({ input }: ToolExecutionPayload<TInput>): Promise<CallToolResult> =>
|
|
391
|
+
handler(this.ynabAPI, this.deltaFetcher, input);
|
|
392
|
+
|
|
393
|
+
const adaptWrite =
|
|
394
|
+
<TInput extends Record<string, unknown>>(
|
|
395
|
+
handler: (
|
|
396
|
+
ynabAPI: ynab.API,
|
|
397
|
+
deltaCache: DeltaCache,
|
|
398
|
+
knowledgeStore: ServerKnowledgeStore,
|
|
399
|
+
params: TInput,
|
|
400
|
+
) => Promise<CallToolResult>,
|
|
401
|
+
) =>
|
|
402
|
+
async ({ input }: ToolExecutionPayload<TInput>): Promise<CallToolResult> =>
|
|
403
|
+
handler(this.ynabAPI, this.deltaCache, this.serverKnowledgeStore, input);
|
|
404
|
+
|
|
405
|
+
const resolveBudgetId = <
|
|
406
|
+
TInput extends { budget_id?: string | undefined },
|
|
407
|
+
>(): DefaultArgumentResolver<TInput> => {
|
|
408
|
+
return ({ rawArguments }) => {
|
|
409
|
+
const provided =
|
|
410
|
+
typeof rawArguments['budget_id'] === 'string' && rawArguments['budget_id'].length > 0
|
|
411
|
+
? (rawArguments['budget_id'] as string)
|
|
412
|
+
: undefined;
|
|
413
|
+
const result = BudgetResolver.resolveBudgetId(provided, this.defaultBudgetId);
|
|
414
|
+
if (typeof result === 'string') {
|
|
415
|
+
return { budget_id: result } as Partial<TInput>;
|
|
416
|
+
}
|
|
417
|
+
throw new DefaultArgumentResolutionError(result);
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const emptyObjectSchema = z.object({}).strict();
|
|
422
|
+
const setDefaultBudgetSchema = z.object({ budget_id: z.string().min(1) }).strict();
|
|
423
|
+
const diagnosticInfoSchema = z
|
|
424
|
+
.object({
|
|
425
|
+
include_memory: z.boolean().default(true),
|
|
426
|
+
include_environment: z.boolean().default(true),
|
|
427
|
+
include_server: z.boolean().default(true),
|
|
428
|
+
include_security: z.boolean().default(true),
|
|
429
|
+
include_cache: z.boolean().default(true),
|
|
430
|
+
include_delta: z.boolean().default(true),
|
|
431
|
+
})
|
|
432
|
+
.strict();
|
|
433
|
+
const setOutputFormatSchema = z
|
|
434
|
+
.object({
|
|
435
|
+
default_minify: z.boolean().optional(),
|
|
436
|
+
pretty_spaces: z.number().int().min(0).max(10).optional(),
|
|
437
|
+
})
|
|
438
|
+
.strict();
|
|
439
|
+
|
|
440
|
+
register({
|
|
441
|
+
name: 'list_budgets',
|
|
442
|
+
description: "List all budgets associated with the user's account",
|
|
443
|
+
inputSchema: emptyObjectSchema,
|
|
444
|
+
outputSchema: ListBudgetsOutputSchema,
|
|
445
|
+
handler: adaptWithDelta(handleListBudgets),
|
|
446
|
+
metadata: {
|
|
447
|
+
annotations: {
|
|
448
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
449
|
+
title: 'YNAB: List Budgets',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
register({
|
|
455
|
+
name: 'get_budget',
|
|
456
|
+
description: 'Get detailed information for a specific budget',
|
|
457
|
+
inputSchema: GetBudgetSchema,
|
|
458
|
+
outputSchema: GetBudgetOutputSchema,
|
|
459
|
+
handler: adapt(handleGetBudget),
|
|
460
|
+
metadata: {
|
|
461
|
+
annotations: {
|
|
462
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
463
|
+
title: 'YNAB: Get Budget Details',
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
register({
|
|
469
|
+
name: 'set_default_budget',
|
|
470
|
+
description: 'Set the default budget for subsequent operations',
|
|
471
|
+
inputSchema: setDefaultBudgetSchema,
|
|
472
|
+
outputSchema: SetDefaultBudgetOutputSchema,
|
|
473
|
+
handler: async ({ input }) => {
|
|
474
|
+
const { budget_id } = input;
|
|
475
|
+
await this.ynabAPI.budgets.getBudgetById(budget_id);
|
|
476
|
+
this.setDefaultBudget(budget_id);
|
|
477
|
+
|
|
478
|
+
// Cache warming for frequently accessed data (fire-and-forget)
|
|
479
|
+
this.warmCacheForBudget(budget_id).catch(() => {
|
|
480
|
+
// Silently handle cache warming errors to not affect main operation
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
content: [
|
|
485
|
+
{
|
|
486
|
+
type: 'text',
|
|
487
|
+
text: responseFormatter.format({
|
|
488
|
+
success: true,
|
|
489
|
+
message: `Default budget set to: ${budget_id}`,
|
|
490
|
+
default_budget_id: budget_id,
|
|
491
|
+
cache_warm_started: true,
|
|
492
|
+
}),
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
};
|
|
496
|
+
},
|
|
497
|
+
metadata: {
|
|
498
|
+
annotations: {
|
|
499
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
500
|
+
title: 'YNAB: Set Default Budget',
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
register({
|
|
506
|
+
name: 'get_default_budget',
|
|
507
|
+
description: 'Get the currently set default budget',
|
|
508
|
+
inputSchema: emptyObjectSchema,
|
|
509
|
+
outputSchema: GetDefaultBudgetOutputSchema,
|
|
510
|
+
handler: async () => {
|
|
511
|
+
try {
|
|
512
|
+
const defaultBudget = this.getDefaultBudget();
|
|
513
|
+
return {
|
|
514
|
+
content: [
|
|
515
|
+
{
|
|
516
|
+
type: 'text',
|
|
517
|
+
text: responseFormatter.format({
|
|
518
|
+
default_budget_id: defaultBudget ?? null,
|
|
519
|
+
has_default: !!defaultBudget,
|
|
520
|
+
message: defaultBudget
|
|
521
|
+
? `Default budget is set to: ${defaultBudget}`
|
|
522
|
+
: 'No default budget is currently set',
|
|
523
|
+
}),
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return this.errorHandler.createValidationError(
|
|
529
|
+
'Error getting default budget',
|
|
530
|
+
error instanceof Error ? error.message : 'Unknown error',
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
metadata: {
|
|
535
|
+
annotations: {
|
|
536
|
+
// Intentionally categorized as UTILITY_LOCAL (not READ_ONLY_EXTERNAL) because
|
|
537
|
+
// this tool only reads local server state without making any YNAB API calls.
|
|
538
|
+
// Compare with set_default_budget which calls ynabAPI.budgets.getBudgetById().
|
|
539
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
540
|
+
title: 'YNAB: Get Default Budget',
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
register({
|
|
546
|
+
name: 'list_accounts',
|
|
547
|
+
description: 'List all accounts for a specific budget (uses default budget if not specified)',
|
|
548
|
+
inputSchema: ListAccountsSchema,
|
|
549
|
+
outputSchema: ListAccountsOutputSchema,
|
|
550
|
+
handler: adaptWithDelta(handleListAccounts),
|
|
551
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListAccountsSchema>>(),
|
|
552
|
+
metadata: {
|
|
553
|
+
annotations: {
|
|
554
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
555
|
+
title: 'YNAB: List Accounts',
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
register({
|
|
561
|
+
name: 'get_account',
|
|
562
|
+
description: 'Get detailed information for a specific account',
|
|
563
|
+
inputSchema: GetAccountSchema,
|
|
564
|
+
outputSchema: GetAccountOutputSchema,
|
|
565
|
+
handler: adapt(handleGetAccount),
|
|
566
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof GetAccountSchema>>(),
|
|
567
|
+
metadata: {
|
|
568
|
+
annotations: {
|
|
569
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
570
|
+
title: 'YNAB: Get Account Details',
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
register({
|
|
576
|
+
name: 'create_account',
|
|
577
|
+
description: 'Create a new account in the specified budget',
|
|
578
|
+
inputSchema: CreateAccountSchema,
|
|
579
|
+
outputSchema: CreateAccountOutputSchema,
|
|
580
|
+
handler: adaptWrite(handleCreateAccount),
|
|
581
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateAccountSchema>>(),
|
|
582
|
+
metadata: {
|
|
583
|
+
annotations: {
|
|
584
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
|
|
585
|
+
title: 'YNAB: Create Account',
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
register({
|
|
591
|
+
name: 'list_transactions',
|
|
592
|
+
description: 'List transactions for a budget with optional filtering',
|
|
593
|
+
inputSchema: ListTransactionsSchema,
|
|
594
|
+
outputSchema: ListTransactionsOutputSchema,
|
|
595
|
+
handler: adaptWithDelta(handleListTransactions),
|
|
596
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListTransactionsSchema>>(),
|
|
597
|
+
metadata: {
|
|
598
|
+
annotations: {
|
|
599
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
600
|
+
title: 'YNAB: List Transactions',
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
register({
|
|
606
|
+
name: 'export_transactions',
|
|
607
|
+
description: 'Export all transactions to a JSON file with descriptive filename',
|
|
608
|
+
inputSchema: ExportTransactionsSchema,
|
|
609
|
+
outputSchema: ExportTransactionsOutputSchema,
|
|
610
|
+
handler: adapt(handleExportTransactions),
|
|
611
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ExportTransactionsSchema>>(),
|
|
612
|
+
metadata: {
|
|
613
|
+
annotations: {
|
|
614
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
615
|
+
title: 'YNAB: Export Transactions',
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
register({
|
|
621
|
+
name: 'compare_transactions',
|
|
622
|
+
description:
|
|
623
|
+
'Compare bank transactions from CSV with YNAB transactions to find missing entries',
|
|
624
|
+
inputSchema: CompareTransactionsSchema,
|
|
625
|
+
outputSchema: CompareTransactionsOutputSchema,
|
|
626
|
+
handler: adapt(handleCompareTransactions),
|
|
627
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CompareTransactionsSchema>>(),
|
|
628
|
+
metadata: {
|
|
629
|
+
annotations: {
|
|
630
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
631
|
+
title: 'YNAB: Compare Transactions',
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
register({
|
|
637
|
+
name: 'reconcile_account',
|
|
638
|
+
description:
|
|
639
|
+
'Guided reconciliation workflow with human narrative, insight detection, and optional execution (create/update/unclear). Set include_structured_data=true to also get full JSON output (large).',
|
|
640
|
+
inputSchema: ReconcileAccountSchema,
|
|
641
|
+
outputSchema: ReconcileAccountOutputSchema,
|
|
642
|
+
handler: adaptWithDelta(handleReconcileAccount),
|
|
643
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ReconcileAccountSchema>>(),
|
|
644
|
+
metadata: {
|
|
645
|
+
annotations: {
|
|
646
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
647
|
+
title: 'YNAB: Reconcile Account',
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
register({
|
|
653
|
+
name: 'get_transaction',
|
|
654
|
+
description: 'Get detailed information for a specific transaction',
|
|
655
|
+
inputSchema: GetTransactionSchema,
|
|
656
|
+
outputSchema: GetTransactionOutputSchema,
|
|
657
|
+
handler: adapt(handleGetTransaction),
|
|
658
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof GetTransactionSchema>>(),
|
|
659
|
+
metadata: {
|
|
660
|
+
annotations: {
|
|
661
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
662
|
+
title: 'YNAB: Get Transaction Details',
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
register({
|
|
668
|
+
name: 'create_transaction',
|
|
669
|
+
description: 'Create a new transaction in the specified budget and account',
|
|
670
|
+
inputSchema: CreateTransactionSchema,
|
|
671
|
+
outputSchema: CreateTransactionOutputSchema,
|
|
672
|
+
handler: adaptWrite(handleCreateTransaction),
|
|
673
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionSchema>>(),
|
|
674
|
+
metadata: {
|
|
675
|
+
annotations: {
|
|
676
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
|
|
677
|
+
title: 'YNAB: Create Transaction',
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
register({
|
|
683
|
+
name: 'create_transactions',
|
|
684
|
+
description:
|
|
685
|
+
'Create multiple transactions in a single batch (1-100 items) with duplicate detection, dry-run validation, and automatic response size management with correlation metadata.',
|
|
686
|
+
inputSchema: CreateTransactionsSchema,
|
|
687
|
+
outputSchema: CreateTransactionsOutputSchema,
|
|
688
|
+
handler: adaptWrite(handleCreateTransactions),
|
|
689
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof CreateTransactionsSchema>>(),
|
|
690
|
+
metadata: {
|
|
691
|
+
annotations: {
|
|
692
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
|
|
693
|
+
title: 'YNAB: Create Multiple Transactions',
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
register({
|
|
699
|
+
name: 'update_transactions',
|
|
700
|
+
description:
|
|
701
|
+
'Update multiple transactions in a single batch (1-100 items) with dry-run validation, automatic cache invalidation, and response size management. Supports optional original_account_id and original_date metadata for efficient cache invalidation.',
|
|
702
|
+
inputSchema: UpdateTransactionsSchema,
|
|
703
|
+
outputSchema: UpdateTransactionsOutputSchema,
|
|
704
|
+
handler: adaptWrite(handleUpdateTransactions),
|
|
705
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionsSchema>>(),
|
|
706
|
+
metadata: {
|
|
707
|
+
annotations: {
|
|
708
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
709
|
+
title: 'YNAB: Update Multiple Transactions',
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
register({
|
|
715
|
+
name: 'create_receipt_split_transaction',
|
|
716
|
+
description: 'Create a split transaction from receipt items with proportional tax allocation',
|
|
717
|
+
inputSchema: CreateReceiptSplitTransactionSchema,
|
|
718
|
+
outputSchema: CreateReceiptSplitTransactionOutputSchema,
|
|
719
|
+
handler: adaptWrite(handleCreateReceiptSplitTransaction),
|
|
720
|
+
defaultArgumentResolver:
|
|
721
|
+
resolveBudgetId<z.infer<typeof CreateReceiptSplitTransactionSchema>>(),
|
|
722
|
+
metadata: {
|
|
723
|
+
annotations: {
|
|
724
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
|
|
725
|
+
title: 'YNAB: Create Split Transaction from Receipt',
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
register({
|
|
731
|
+
name: 'update_transaction',
|
|
732
|
+
description: 'Update an existing transaction',
|
|
733
|
+
inputSchema: UpdateTransactionSchema,
|
|
734
|
+
outputSchema: UpdateTransactionOutputSchema,
|
|
735
|
+
handler: adaptWrite(handleUpdateTransaction),
|
|
736
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateTransactionSchema>>(),
|
|
737
|
+
metadata: {
|
|
738
|
+
annotations: {
|
|
739
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
740
|
+
title: 'YNAB: Update Transaction',
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
register({
|
|
746
|
+
name: 'delete_transaction',
|
|
747
|
+
description: 'Delete a transaction from the specified budget',
|
|
748
|
+
inputSchema: DeleteTransactionSchema,
|
|
749
|
+
outputSchema: DeleteTransactionOutputSchema,
|
|
750
|
+
handler: adaptWrite(handleDeleteTransaction),
|
|
751
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof DeleteTransactionSchema>>(),
|
|
752
|
+
metadata: {
|
|
753
|
+
annotations: {
|
|
754
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_DELETE,
|
|
755
|
+
title: 'YNAB: Delete Transaction',
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
register({
|
|
761
|
+
name: 'list_categories',
|
|
762
|
+
description: 'List all categories for a specific budget',
|
|
763
|
+
inputSchema: ListCategoriesSchema,
|
|
764
|
+
outputSchema: ListCategoriesOutputSchema,
|
|
765
|
+
handler: adaptWithDelta(handleListCategories),
|
|
766
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListCategoriesSchema>>(),
|
|
767
|
+
metadata: {
|
|
768
|
+
annotations: {
|
|
769
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
770
|
+
title: 'YNAB: List Categories',
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
register({
|
|
776
|
+
name: 'get_category',
|
|
777
|
+
description: 'Get detailed information for a specific category',
|
|
778
|
+
inputSchema: GetCategorySchema,
|
|
779
|
+
outputSchema: GetCategoryOutputSchema,
|
|
780
|
+
handler: adapt(handleGetCategory),
|
|
781
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof GetCategorySchema>>(),
|
|
782
|
+
metadata: {
|
|
783
|
+
annotations: {
|
|
784
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
785
|
+
title: 'YNAB: Get Category Details',
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
register({
|
|
791
|
+
name: 'update_category',
|
|
792
|
+
description: 'Update the budgeted amount for a category in the current month',
|
|
793
|
+
inputSchema: UpdateCategorySchema,
|
|
794
|
+
outputSchema: UpdateCategoryOutputSchema,
|
|
795
|
+
handler: adaptWrite(handleUpdateCategory),
|
|
796
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof UpdateCategorySchema>>(),
|
|
797
|
+
metadata: {
|
|
798
|
+
annotations: {
|
|
799
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
800
|
+
title: 'YNAB: Update Category Budget',
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
register({
|
|
806
|
+
name: 'list_payees',
|
|
807
|
+
description: 'List all payees for a specific budget',
|
|
808
|
+
inputSchema: ListPayeesSchema,
|
|
809
|
+
outputSchema: ListPayeesOutputSchema,
|
|
810
|
+
handler: adaptWithDelta(handleListPayees),
|
|
811
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListPayeesSchema>>(),
|
|
812
|
+
metadata: {
|
|
813
|
+
annotations: {
|
|
814
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
815
|
+
title: 'YNAB: List Payees',
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
register({
|
|
821
|
+
name: 'get_payee',
|
|
822
|
+
description: 'Get detailed information for a specific payee',
|
|
823
|
+
inputSchema: GetPayeeSchema,
|
|
824
|
+
outputSchema: GetPayeeOutputSchema,
|
|
825
|
+
handler: adapt(handleGetPayee),
|
|
826
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof GetPayeeSchema>>(),
|
|
827
|
+
metadata: {
|
|
828
|
+
annotations: {
|
|
829
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
830
|
+
title: 'YNAB: Get Payee Details',
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
register({
|
|
836
|
+
name: 'get_month',
|
|
837
|
+
description: 'Get budget data for a specific month',
|
|
838
|
+
inputSchema: GetMonthSchema,
|
|
839
|
+
outputSchema: GetMonthOutputSchema,
|
|
840
|
+
handler: adapt(handleGetMonth),
|
|
841
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof GetMonthSchema>>(),
|
|
842
|
+
metadata: {
|
|
843
|
+
annotations: {
|
|
844
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
845
|
+
title: 'YNAB: Get Month Budget Data',
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
register({
|
|
851
|
+
name: 'list_months',
|
|
852
|
+
description: 'List all months summary data for a budget',
|
|
853
|
+
inputSchema: ListMonthsSchema,
|
|
854
|
+
outputSchema: ListMonthsOutputSchema,
|
|
855
|
+
handler: adaptWithDelta(handleListMonths),
|
|
856
|
+
defaultArgumentResolver: resolveBudgetId<z.infer<typeof ListMonthsSchema>>(),
|
|
857
|
+
metadata: {
|
|
858
|
+
annotations: {
|
|
859
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
860
|
+
title: 'YNAB: List Months',
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
register({
|
|
866
|
+
name: 'get_user',
|
|
867
|
+
description: 'Get information about the authenticated user',
|
|
868
|
+
inputSchema: emptyObjectSchema,
|
|
869
|
+
outputSchema: GetUserOutputSchema,
|
|
870
|
+
handler: adaptNoInput(handleGetUser),
|
|
871
|
+
metadata: {
|
|
872
|
+
annotations: {
|
|
873
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
874
|
+
title: 'YNAB: Get User Information',
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
register({
|
|
880
|
+
name: 'convert_amount',
|
|
881
|
+
description: 'Convert between dollars and milliunits with integer arithmetic for precision',
|
|
882
|
+
inputSchema: ConvertAmountSchema,
|
|
883
|
+
outputSchema: ConvertAmountOutputSchema,
|
|
884
|
+
handler: async ({ input }) => handleConvertAmount(input),
|
|
885
|
+
metadata: {
|
|
886
|
+
annotations: {
|
|
887
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
888
|
+
title: 'YNAB: Convert Amount',
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
register({
|
|
894
|
+
name: 'diagnostic_info',
|
|
895
|
+
description: 'Get comprehensive diagnostic information about the MCP server',
|
|
896
|
+
inputSchema: diagnosticInfoSchema,
|
|
897
|
+
outputSchema: DiagnosticInfoOutputSchema,
|
|
898
|
+
handler: async ({ input }) => {
|
|
899
|
+
return this.diagnosticManager.collectDiagnostics(input);
|
|
900
|
+
},
|
|
901
|
+
metadata: {
|
|
902
|
+
annotations: {
|
|
903
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
904
|
+
title: 'YNAB: Diagnostic Information',
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
register({
|
|
910
|
+
name: 'clear_cache',
|
|
911
|
+
description: 'Clear the in-memory cache (safe, no YNAB data is modified)',
|
|
912
|
+
inputSchema: emptyObjectSchema,
|
|
913
|
+
outputSchema: ClearCacheOutputSchema,
|
|
914
|
+
handler: async () => {
|
|
915
|
+
cacheManager.clear();
|
|
916
|
+
return {
|
|
917
|
+
content: [{ type: 'text', text: responseFormatter.format({ success: true }) }],
|
|
918
|
+
};
|
|
919
|
+
},
|
|
920
|
+
metadata: {
|
|
921
|
+
annotations: {
|
|
922
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
923
|
+
title: 'YNAB: Clear Cache',
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
register({
|
|
929
|
+
name: 'set_output_format',
|
|
930
|
+
description: 'Configure default JSON output formatting (minify or pretty spaces)',
|
|
931
|
+
inputSchema: setOutputFormatSchema,
|
|
932
|
+
outputSchema: SetOutputFormatOutputSchema,
|
|
933
|
+
handler: async ({ input }) => {
|
|
934
|
+
const options: { defaultMinify?: boolean; prettySpaces?: number } = {};
|
|
935
|
+
if (typeof input.default_minify === 'boolean') {
|
|
936
|
+
options.defaultMinify = input.default_minify;
|
|
937
|
+
}
|
|
938
|
+
if (typeof input.pretty_spaces === 'number') {
|
|
939
|
+
options.prettySpaces = Math.max(0, Math.min(10, Math.floor(input.pretty_spaces)));
|
|
940
|
+
}
|
|
941
|
+
responseFormatter.configure(options);
|
|
942
|
+
|
|
943
|
+
// Build human-readable message describing the new configuration
|
|
944
|
+
const parts: string[] = [];
|
|
945
|
+
if (options.defaultMinify !== undefined) {
|
|
946
|
+
parts.push(`minify=${options.defaultMinify}`);
|
|
947
|
+
}
|
|
948
|
+
if (options.prettySpaces !== undefined) {
|
|
949
|
+
parts.push(`spaces=${options.prettySpaces}`);
|
|
950
|
+
}
|
|
951
|
+
const message =
|
|
952
|
+
parts.length > 0
|
|
953
|
+
? `Output format configured: ${parts.join(', ')}`
|
|
954
|
+
: 'Output format configured';
|
|
955
|
+
|
|
956
|
+
return {
|
|
957
|
+
content: [
|
|
958
|
+
{
|
|
959
|
+
type: 'text',
|
|
960
|
+
text: responseFormatter.format({ success: true, message, options }),
|
|
961
|
+
},
|
|
962
|
+
],
|
|
963
|
+
};
|
|
964
|
+
},
|
|
965
|
+
metadata: {
|
|
966
|
+
annotations: {
|
|
967
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
968
|
+
title: 'YNAB: Set Output Format',
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private extractMinifyOverride(args: Record<string, unknown> | undefined): boolean | undefined {
|
|
975
|
+
if (!args) {
|
|
976
|
+
return undefined;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
for (const key of ['minify', '_minify', '__minify'] as const) {
|
|
980
|
+
const value = args[key];
|
|
981
|
+
if (typeof value === 'boolean') {
|
|
982
|
+
return value;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return undefined;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Starts the MCP server with stdio transport
|
|
991
|
+
*/
|
|
992
|
+
async run(): Promise<void> {
|
|
993
|
+
try {
|
|
994
|
+
// Validate token before starting server
|
|
995
|
+
await this.validateToken();
|
|
996
|
+
|
|
997
|
+
const transport = new StdioServerTransport();
|
|
998
|
+
await this.server.connect(transport);
|
|
999
|
+
|
|
1000
|
+
console.error('YNAB MCP Server started successfully');
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
if (error instanceof AuthenticationError || error instanceof ConfigurationError) {
|
|
1003
|
+
console.error(`Server startup failed: ${error.message}`);
|
|
1004
|
+
if (this.exitOnError) {
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
} else {
|
|
1007
|
+
throw error;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Gets the YNAB API instance (for testing purposes)
|
|
1016
|
+
*/
|
|
1017
|
+
getYNABAPI(): ynab.API {
|
|
1018
|
+
return this.ynabAPI;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Gets the MCP server instance (for testing purposes)
|
|
1023
|
+
*/
|
|
1024
|
+
getServer(): Server {
|
|
1025
|
+
return this.server;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Sets the default budget ID for operations
|
|
1030
|
+
*/
|
|
1031
|
+
setDefaultBudget(budgetId: string): void {
|
|
1032
|
+
this.defaultBudgetId = budgetId;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Gets the default budget ID
|
|
1037
|
+
*/
|
|
1038
|
+
getDefaultBudget(): string | undefined {
|
|
1039
|
+
return this.defaultBudgetId;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Clears the default budget ID (primarily for testing purposes)
|
|
1044
|
+
*/
|
|
1045
|
+
clearDefaultBudget(): void {
|
|
1046
|
+
this.defaultBudgetId = undefined;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Gets the tool registry instance (for testing purposes)
|
|
1051
|
+
*/
|
|
1052
|
+
getToolRegistry(): ToolRegistry {
|
|
1053
|
+
return this.toolRegistry;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Gets the budget ID to use - either provided or default
|
|
1058
|
+
*
|
|
1059
|
+
* @deprecated This method is deprecated and should not be used.
|
|
1060
|
+
* Use BudgetResolver.resolveBudgetId() directly instead, which returns
|
|
1061
|
+
* a CallToolResult for errors rather than throwing exceptions.
|
|
1062
|
+
*
|
|
1063
|
+
* @returns The resolved budget ID string or throws ValidationError
|
|
1064
|
+
*/
|
|
1065
|
+
getBudgetId(providedBudgetId?: string): string {
|
|
1066
|
+
const result = BudgetResolver.resolveBudgetId(providedBudgetId, this.defaultBudgetId);
|
|
1067
|
+
if (typeof result === 'string') {
|
|
1068
|
+
return result;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Convert CallToolResult to ValidationError for consistency with ErrorHandler
|
|
1072
|
+
const errorText =
|
|
1073
|
+
result.content?.[0]?.type === 'text' ? result.content[0].text : 'Budget resolution failed';
|
|
1074
|
+
const parsedError = (() => {
|
|
1075
|
+
try {
|
|
1076
|
+
return JSON.parse(errorText);
|
|
1077
|
+
} catch {
|
|
1078
|
+
return { error: { message: errorText } };
|
|
1079
|
+
}
|
|
1080
|
+
})();
|
|
1081
|
+
|
|
1082
|
+
const message = parsedError.error?.message || 'Budget resolution failed';
|
|
1083
|
+
throw new ValidationError(message);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Warm cache for frequently accessed data after setting default budget
|
|
1088
|
+
* Uses fire-and-forget pattern to avoid blocking the main operation
|
|
1089
|
+
* Runs cache warming operations in parallel for faster completion
|
|
1090
|
+
*/
|
|
1091
|
+
private async warmCacheForBudget(budgetId: string): Promise<void> {
|
|
1092
|
+
try {
|
|
1093
|
+
// Run all cache warming operations in parallel
|
|
1094
|
+
await Promise.all([
|
|
1095
|
+
this.deltaFetcher.fetchAccounts(budgetId, { forceFullRefresh: true }),
|
|
1096
|
+
this.deltaFetcher.fetchCategories(budgetId, { forceFullRefresh: true }),
|
|
1097
|
+
this.deltaFetcher.fetchPayees(budgetId, { forceFullRefresh: true }),
|
|
1098
|
+
]);
|
|
1099
|
+
} catch {
|
|
1100
|
+
// Cache warming failures should not affect the main operation
|
|
1101
|
+
// Errors are handled by the caller with a catch block
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Public handler methods for testing and external access
|
|
1107
|
+
*/
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Handle list tools request - public method for testing
|
|
1111
|
+
*/
|
|
1112
|
+
public async handleListTools() {
|
|
1113
|
+
return {
|
|
1114
|
+
tools: this.toolRegistry.listTools(),
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* Handle list resources request - public method for testing
|
|
1120
|
+
*/
|
|
1121
|
+
public async handleListResources() {
|
|
1122
|
+
return this.resourceManager.listResources();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Handle read resource request - public method for testing
|
|
1127
|
+
*/
|
|
1128
|
+
public async handleReadResource(params: { uri: string }) {
|
|
1129
|
+
const { uri } = params;
|
|
1130
|
+
try {
|
|
1131
|
+
return await this.resourceManager.readResource(uri);
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
return this.errorHandler.handleError(error, `reading resource: ${uri}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Handle list prompts request - public method for testing
|
|
1139
|
+
*/
|
|
1140
|
+
public async handleListPrompts() {
|
|
1141
|
+
return this.promptManager.listPrompts();
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Handle get prompt request - public method for testing
|
|
1146
|
+
*/
|
|
1147
|
+
public async handleGetPrompt(params: { name: string; arguments?: Record<string, unknown> }) {
|
|
1148
|
+
const { name, arguments: args } = params;
|
|
1149
|
+
try {
|
|
1150
|
+
const prompt = await this.promptManager.getPrompt(name, args);
|
|
1151
|
+
const tools = Array.isArray((prompt as { tools?: unknown[] }).tools)
|
|
1152
|
+
? ((prompt as { tools?: unknown[] }).tools as Tool[])
|
|
1153
|
+
: undefined;
|
|
1154
|
+
return tools ? { ...prompt, tools } : prompt;
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
return this.errorHandler.handleError(error, `getting prompt: ${name}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Try to read the package version for accurate server metadata
|
|
1162
|
+
*/
|
|
1163
|
+
private readPackageVersion(): string | null {
|
|
1164
|
+
const candidates = [path.resolve(process.cwd(), 'package.json')];
|
|
1165
|
+
try {
|
|
1166
|
+
// May fail in bundled CJS builds; guard accordingly
|
|
1167
|
+
const metaUrl = (import.meta as unknown as { url?: string })?.url;
|
|
1168
|
+
if (metaUrl) {
|
|
1169
|
+
const maybe = path.resolve(path.dirname(new URL(metaUrl).pathname), '../../package.json');
|
|
1170
|
+
candidates.push(maybe);
|
|
1171
|
+
}
|
|
1172
|
+
} catch {
|
|
1173
|
+
// ignore
|
|
1174
|
+
}
|
|
1175
|
+
for (const p of candidates) {
|
|
1176
|
+
try {
|
|
1177
|
+
if (fs.existsSync(p)) {
|
|
1178
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
1179
|
+
const pkg = JSON.parse(raw) as { version?: string };
|
|
1180
|
+
if (pkg.version && typeof pkg.version === 'string') return pkg.version;
|
|
1181
|
+
}
|
|
1182
|
+
} catch {
|
|
1183
|
+
// ignore and try next
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|