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