@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,205 @@
|
|
|
1
|
+
const defaultPromptDefinitions = [
|
|
2
|
+
{
|
|
3
|
+
name: 'create-transaction',
|
|
4
|
+
description: 'Create a new transaction in YNAB',
|
|
5
|
+
arguments: [
|
|
6
|
+
{
|
|
7
|
+
name: 'budget_name',
|
|
8
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
9
|
+
required: false,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'account_name',
|
|
13
|
+
description: 'Name of the account',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'amount',
|
|
18
|
+
description: 'Transaction amount (negative for expenses, positive for income)',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'payee',
|
|
23
|
+
description: 'Who you paid or received money from',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'category',
|
|
28
|
+
description: 'Budget category (optional)',
|
|
29
|
+
required: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'memo',
|
|
33
|
+
description: 'Additional notes (optional)',
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'budget-summary',
|
|
40
|
+
description: 'Get a summary of your budget status',
|
|
41
|
+
arguments: [
|
|
42
|
+
{
|
|
43
|
+
name: 'budget_name',
|
|
44
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
45
|
+
required: false,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'month',
|
|
49
|
+
description: 'Month to analyze (YYYY-MM format, optional, uses current month if not specified)',
|
|
50
|
+
required: false,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'account-balances',
|
|
56
|
+
description: 'Check balances across all accounts',
|
|
57
|
+
arguments: [
|
|
58
|
+
{
|
|
59
|
+
name: 'budget_name',
|
|
60
|
+
description: 'Name of the budget (optional, uses first budget if not specified)',
|
|
61
|
+
required: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'account_type',
|
|
65
|
+
description: 'Filter by account type (checking, savings, creditCard, etc.)',
|
|
66
|
+
required: false,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const defaultPromptHandlers = {
|
|
72
|
+
'create-transaction': async (_name, args) => {
|
|
73
|
+
const budgetName = args?.['budget_name'] || 'first available budget';
|
|
74
|
+
const accountName = args?.['account_name'] || '[ACCOUNT_NAME]';
|
|
75
|
+
const amount = args?.['amount'] || '[AMOUNT]';
|
|
76
|
+
const payee = args?.['payee'] || '[PAYEE]';
|
|
77
|
+
const category = args?.['category'] || '[CATEGORY]';
|
|
78
|
+
const memo = args?.['memo'] || '';
|
|
79
|
+
return {
|
|
80
|
+
description: `Create a transaction for ${payee} in ${accountName}`,
|
|
81
|
+
messages: [
|
|
82
|
+
{
|
|
83
|
+
role: 'user',
|
|
84
|
+
content: {
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: `Please create a transaction with the following details:
|
|
87
|
+
- Budget: ${budgetName}
|
|
88
|
+
- Account: ${accountName}
|
|
89
|
+
- Amount: $${amount}
|
|
90
|
+
- Payee: ${payee}
|
|
91
|
+
- Category: ${category}
|
|
92
|
+
- Memo: ${memo}
|
|
93
|
+
|
|
94
|
+
Use the appropriate YNAB MCP tools to:
|
|
95
|
+
1. First, list budgets to find the budget ID
|
|
96
|
+
2. List accounts for that budget to find the account ID
|
|
97
|
+
3. If a category is specified, list categories to find the category ID
|
|
98
|
+
4. Create the transaction with the correct amount in milliunits (multiply by 1000)
|
|
99
|
+
5. Confirm the transaction was created successfully`,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
'budget-summary': async (_name, args) => {
|
|
106
|
+
const summaryBudget = args?.['budget_name'] || 'first available budget';
|
|
107
|
+
const month = args?.['month'] || 'current month';
|
|
108
|
+
return {
|
|
109
|
+
description: `Get budget summary for ${summaryBudget}`,
|
|
110
|
+
messages: [
|
|
111
|
+
{
|
|
112
|
+
role: 'user',
|
|
113
|
+
content: {
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: `Please provide a comprehensive budget summary for ${summaryBudget} (${month}):
|
|
116
|
+
|
|
117
|
+
IMPORTANT: In YNAB, understand these key fields:
|
|
118
|
+
- budgeted: Amount assigned to the category this month
|
|
119
|
+
- activity: Spending/income in the category this month (negative = spending)
|
|
120
|
+
- balance: Available amount in the category = previous balance + budgeted + activity
|
|
121
|
+
- OVERSPENDING occurs when balance < 0 (Available goes negative), NOT when spending > budgeted for the month
|
|
122
|
+
|
|
123
|
+
SPENDING TRENDS: The analysis uses linear regression over multiple months to detect real spending patterns. Each trend includes:
|
|
124
|
+
- explanation: User-friendly description of what the trend means
|
|
125
|
+
- reliability_score: Confidence level (0-100%) indicating how reliable the trend is
|
|
126
|
+
- data_points: Number of months used in the analysis
|
|
127
|
+
Focus on trends with high reliability scores for actionable insights.
|
|
128
|
+
|
|
129
|
+
BUDGET OPTIMIZATION: The system provides three types of optimization insights:
|
|
130
|
+
1. "Consistently Under-Spent Categories" - Based on multi-month historical trends (reliable patterns)
|
|
131
|
+
2. "Categories Over Monthly Assignment" - Current month only (spending > budgeted but Available still positive)
|
|
132
|
+
3. "Large Unused Category Balances" - Categories with substantial unused funds
|
|
133
|
+
Distinguish between current-month patterns vs historical trends when presenting insights.
|
|
134
|
+
|
|
135
|
+
1. List all budgets and select the appropriate one
|
|
136
|
+
2. Get monthly data for ${month}
|
|
137
|
+
3. List categories to show budget vs actual spending
|
|
138
|
+
4. Provide insights on:
|
|
139
|
+
- Total budgeted vs actual spending
|
|
140
|
+
- Categories where Available balance is negative (true overspending - when the category's balance field is < 0)
|
|
141
|
+
- Categories where spending exceeded this month's assignment (but still have positive Available balance)
|
|
142
|
+
- Available money to budget
|
|
143
|
+
- Any true overspending where categories went into the red (negative Available balance)
|
|
144
|
+
|
|
145
|
+
Format the response in a clear, easy-to-read summary.`,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
'account-balances': async (_name, args) => {
|
|
152
|
+
const balanceBudget = args?.['budget_name'] || 'first available budget';
|
|
153
|
+
const accountType = args?.['account_type'] || 'all accounts';
|
|
154
|
+
return {
|
|
155
|
+
description: `Check account balances for ${accountType}`,
|
|
156
|
+
messages: [
|
|
157
|
+
{
|
|
158
|
+
role: 'user',
|
|
159
|
+
content: {
|
|
160
|
+
type: 'text',
|
|
161
|
+
text: `Please show account balances for ${balanceBudget}:
|
|
162
|
+
|
|
163
|
+
1. List all budgets and select the appropriate one
|
|
164
|
+
2. List accounts for that budget
|
|
165
|
+
3. Filter by account type: ${accountType}
|
|
166
|
+
4. Show balances in a clear format with:
|
|
167
|
+
- Account name and type
|
|
168
|
+
- Current balance
|
|
169
|
+
- Cleared vs uncleared amounts
|
|
170
|
+
- Total by account type
|
|
171
|
+
- Net worth summary (assets - liabilities)
|
|
172
|
+
|
|
173
|
+
Convert milliunits to dollars for easy reading.`,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
export class PromptManager {
|
|
181
|
+
constructor() {
|
|
182
|
+
this.promptDefinitions = [...defaultPromptDefinitions];
|
|
183
|
+
this.promptHandlers = { ...defaultPromptHandlers };
|
|
184
|
+
}
|
|
185
|
+
registerPrompt(definition, handler) {
|
|
186
|
+
this.promptDefinitions.push(definition);
|
|
187
|
+
this.promptHandlers[definition.name] = handler;
|
|
188
|
+
}
|
|
189
|
+
listPrompts() {
|
|
190
|
+
return {
|
|
191
|
+
prompts: this.promptDefinitions,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async getPrompt(name, args) {
|
|
195
|
+
const handler = this.promptHandlers[name];
|
|
196
|
+
if (!handler) {
|
|
197
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
198
|
+
}
|
|
199
|
+
const definition = this.promptDefinitions.find((p) => p.name === name);
|
|
200
|
+
if (!definition) {
|
|
201
|
+
throw new Error(`Prompt definition not found: ${name}`);
|
|
202
|
+
}
|
|
203
|
+
return await handler(name, args);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface RateLimitConfig {
|
|
2
|
+
maxRequests: number;
|
|
3
|
+
windowMs: number;
|
|
4
|
+
enableLogging?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface RateLimitInfo {
|
|
7
|
+
remaining: number;
|
|
8
|
+
resetTime: Date;
|
|
9
|
+
isLimited: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class RateLimiter {
|
|
12
|
+
private requests;
|
|
13
|
+
private config;
|
|
14
|
+
constructor(config?: Partial<RateLimitConfig>);
|
|
15
|
+
isAllowed(identifier: string): RateLimitInfo;
|
|
16
|
+
recordRequest(identifier: string): void;
|
|
17
|
+
getStatus(identifier: string): RateLimitInfo;
|
|
18
|
+
reset(identifier?: string): void;
|
|
19
|
+
cleanup(): void;
|
|
20
|
+
private hashIdentifier;
|
|
21
|
+
}
|
|
22
|
+
export declare class RateLimitError extends Error {
|
|
23
|
+
readonly resetTime: Date;
|
|
24
|
+
readonly remaining: number;
|
|
25
|
+
constructor(message: string, resetTime: Date, remaining?: number);
|
|
26
|
+
}
|
|
27
|
+
export declare const globalRateLimiter: RateLimiter;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export class RateLimiter {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.requests = new Map();
|
|
4
|
+
this.config = {
|
|
5
|
+
maxRequests: 200,
|
|
6
|
+
windowMs: 60 * 60 * 1000,
|
|
7
|
+
enableLogging: false,
|
|
8
|
+
...config,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
isAllowed(identifier) {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
const windowStart = now - this.config.windowMs;
|
|
14
|
+
let requests = this.requests.get(identifier) || [];
|
|
15
|
+
requests = requests.filter((timestamp) => timestamp > windowStart);
|
|
16
|
+
this.requests.set(identifier, requests);
|
|
17
|
+
const remaining = Math.max(0, this.config.maxRequests - requests.length);
|
|
18
|
+
const resetTime = new Date(now + this.config.windowMs);
|
|
19
|
+
const isLimited = requests.length >= this.config.maxRequests;
|
|
20
|
+
if (this.config.enableLogging) {
|
|
21
|
+
console.error(`Rate limit check for ${this.hashIdentifier(identifier)}: ${requests.length}/${this.config.maxRequests} requests, remaining: ${remaining}, limited: ${isLimited}`);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
remaining,
|
|
25
|
+
resetTime,
|
|
26
|
+
isLimited,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
recordRequest(identifier) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const requests = this.requests.get(identifier) || [];
|
|
32
|
+
requests.push(now);
|
|
33
|
+
this.requests.set(identifier, requests);
|
|
34
|
+
if (this.config.enableLogging) {
|
|
35
|
+
console.error(`Recorded request for ${this.hashIdentifier(identifier)}: ${requests.length}/${this.config.maxRequests} requests`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getStatus(identifier) {
|
|
39
|
+
return this.isAllowed(identifier);
|
|
40
|
+
}
|
|
41
|
+
reset(identifier) {
|
|
42
|
+
if (identifier) {
|
|
43
|
+
this.requests.delete(identifier);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.requests.clear();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
cleanup() {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const windowStart = now - this.config.windowMs;
|
|
52
|
+
for (const [identifier, requests] of this.requests.entries()) {
|
|
53
|
+
const validRequests = requests.filter((timestamp) => timestamp > windowStart);
|
|
54
|
+
if (validRequests.length === 0) {
|
|
55
|
+
this.requests.delete(identifier);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.requests.set(identifier, validRequests);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
hashIdentifier(identifier) {
|
|
63
|
+
let hash = 0;
|
|
64
|
+
for (let i = 0; i < identifier.length; i++) {
|
|
65
|
+
const char = identifier.charCodeAt(i);
|
|
66
|
+
hash = (hash << 5) - hash + char;
|
|
67
|
+
hash = hash & hash;
|
|
68
|
+
}
|
|
69
|
+
return `token_${Math.abs(hash).toString(16)}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class RateLimitError extends Error {
|
|
73
|
+
constructor(message, resetTime, remaining = 0) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.resetTime = resetTime;
|
|
76
|
+
this.remaining = remaining;
|
|
77
|
+
this.name = 'RateLimitError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export const globalRateLimiter = new RateLimiter({
|
|
81
|
+
enableLogging: process.env['NODE_ENV'] !== 'production',
|
|
82
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
timestamp: Date;
|
|
3
|
+
toolName: string;
|
|
4
|
+
operation: string;
|
|
5
|
+
parameters: Record<string, unknown>;
|
|
6
|
+
success: boolean;
|
|
7
|
+
duration?: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
rateLimitInfo?: {
|
|
10
|
+
remaining: number;
|
|
11
|
+
isLimited: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface LoggerConfig {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
logLevel: 'error' | 'warn' | 'info' | 'debug';
|
|
17
|
+
maxLogEntries: number;
|
|
18
|
+
sanitizeParameters: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare class RequestLogger {
|
|
21
|
+
private logs;
|
|
22
|
+
private config;
|
|
23
|
+
constructor(config?: Partial<LoggerConfig>);
|
|
24
|
+
logRequest(toolName: string, operation: string, parameters: Record<string, unknown>, success: boolean, duration?: number, error?: string, rateLimitInfo?: {
|
|
25
|
+
remaining: number;
|
|
26
|
+
isLimited: boolean;
|
|
27
|
+
}): void;
|
|
28
|
+
logSuccess(toolName: string, operation: string, parameters: Record<string, unknown>, duration?: number, rateLimitInfo?: {
|
|
29
|
+
remaining: number;
|
|
30
|
+
isLimited: boolean;
|
|
31
|
+
}): void;
|
|
32
|
+
logError(toolName: string, operation: string, parameters: Record<string, unknown>, error: string, duration?: number, rateLimitInfo?: {
|
|
33
|
+
remaining: number;
|
|
34
|
+
isLimited: boolean;
|
|
35
|
+
}): void;
|
|
36
|
+
getRecentLogs(count?: number): LogEntry[];
|
|
37
|
+
getFilteredLogs(filter: {
|
|
38
|
+
toolName?: string;
|
|
39
|
+
success?: boolean;
|
|
40
|
+
since?: Date;
|
|
41
|
+
limit?: number;
|
|
42
|
+
}): LogEntry[];
|
|
43
|
+
clearLogs(): void;
|
|
44
|
+
getStats(): {
|
|
45
|
+
totalRequests: number;
|
|
46
|
+
successfulRequests: number;
|
|
47
|
+
failedRequests: number;
|
|
48
|
+
averageDuration: number;
|
|
49
|
+
rateLimitedRequests: number;
|
|
50
|
+
toolUsage: Record<string, number>;
|
|
51
|
+
};
|
|
52
|
+
private sanitizeParameters;
|
|
53
|
+
private isSensitiveParameter;
|
|
54
|
+
private sanitizeString;
|
|
55
|
+
private sanitizeObject;
|
|
56
|
+
private sanitizeError;
|
|
57
|
+
private outputLog;
|
|
58
|
+
private shouldLog;
|
|
59
|
+
private formatLogMessage;
|
|
60
|
+
}
|
|
61
|
+
export declare const globalRequestLogger: RequestLogger;
|
|
62
|
+
export default globalRequestLogger;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export class RequestLogger {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.logs = [];
|
|
4
|
+
this.config = {
|
|
5
|
+
enabled: process.env['NODE_ENV'] !== 'production',
|
|
6
|
+
logLevel: process.env['LOG_LEVEL'] || 'info',
|
|
7
|
+
maxLogEntries: 1000,
|
|
8
|
+
sanitizeParameters: true,
|
|
9
|
+
...config,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
logRequest(toolName, operation, parameters, success, duration, error, rateLimitInfo) {
|
|
13
|
+
if (!this.config.enabled)
|
|
14
|
+
return;
|
|
15
|
+
const logEntry = {
|
|
16
|
+
timestamp: new Date(),
|
|
17
|
+
toolName,
|
|
18
|
+
operation,
|
|
19
|
+
parameters: this.config.sanitizeParameters ? this.sanitizeParameters(parameters) : parameters,
|
|
20
|
+
success,
|
|
21
|
+
...(duration !== undefined && { duration }),
|
|
22
|
+
...(error && { error: this.sanitizeError(error) }),
|
|
23
|
+
...(rateLimitInfo && { rateLimitInfo }),
|
|
24
|
+
};
|
|
25
|
+
this.logs.push(logEntry);
|
|
26
|
+
if (this.logs.length > this.config.maxLogEntries) {
|
|
27
|
+
this.logs.shift();
|
|
28
|
+
}
|
|
29
|
+
this.outputLog(logEntry);
|
|
30
|
+
}
|
|
31
|
+
logSuccess(toolName, operation, parameters, duration, rateLimitInfo) {
|
|
32
|
+
this.logRequest(toolName, operation, parameters, true, duration, undefined, rateLimitInfo);
|
|
33
|
+
}
|
|
34
|
+
logError(toolName, operation, parameters, error, duration, rateLimitInfo) {
|
|
35
|
+
this.logRequest(toolName, operation, parameters, false, duration, error, rateLimitInfo);
|
|
36
|
+
}
|
|
37
|
+
getRecentLogs(count = 50) {
|
|
38
|
+
return this.logs.slice(-count);
|
|
39
|
+
}
|
|
40
|
+
getFilteredLogs(filter) {
|
|
41
|
+
let filtered = this.logs;
|
|
42
|
+
if (filter.toolName) {
|
|
43
|
+
filtered = filtered.filter((log) => log.toolName === filter.toolName);
|
|
44
|
+
}
|
|
45
|
+
if (filter.success !== undefined) {
|
|
46
|
+
filtered = filtered.filter((log) => log.success === filter.success);
|
|
47
|
+
}
|
|
48
|
+
if (filter.since) {
|
|
49
|
+
filtered = filtered.filter((log) => log.timestamp >= filter.since);
|
|
50
|
+
}
|
|
51
|
+
if (filter.limit) {
|
|
52
|
+
filtered = filtered.slice(-filter.limit);
|
|
53
|
+
}
|
|
54
|
+
return filtered;
|
|
55
|
+
}
|
|
56
|
+
clearLogs() {
|
|
57
|
+
this.logs = [];
|
|
58
|
+
}
|
|
59
|
+
getStats() {
|
|
60
|
+
const totalRequests = this.logs.length;
|
|
61
|
+
const successfulRequests = this.logs.filter((log) => log.success).length;
|
|
62
|
+
const failedRequests = totalRequests - successfulRequests;
|
|
63
|
+
const durationsWithValues = this.logs
|
|
64
|
+
.filter((log) => log.duration !== undefined)
|
|
65
|
+
.map((log) => log.duration);
|
|
66
|
+
const averageDuration = durationsWithValues.length > 0
|
|
67
|
+
? durationsWithValues.reduce((sum, duration) => sum + duration, 0) /
|
|
68
|
+
durationsWithValues.length
|
|
69
|
+
: 0;
|
|
70
|
+
const rateLimitedRequests = this.logs.filter((log) => log.rateLimitInfo?.isLimited).length;
|
|
71
|
+
const toolUsage = {};
|
|
72
|
+
this.logs.forEach((log) => {
|
|
73
|
+
toolUsage[log.toolName] = (toolUsage[log.toolName] || 0) + 1;
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
totalRequests,
|
|
77
|
+
successfulRequests,
|
|
78
|
+
failedRequests,
|
|
79
|
+
averageDuration,
|
|
80
|
+
rateLimitedRequests,
|
|
81
|
+
toolUsage,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
sanitizeParameters(parameters) {
|
|
85
|
+
const sanitized = {};
|
|
86
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
87
|
+
if (this.isSensitiveParameter(key)) {
|
|
88
|
+
sanitized[key] = '***';
|
|
89
|
+
}
|
|
90
|
+
else if (typeof value === 'string') {
|
|
91
|
+
sanitized[key] = this.sanitizeString(value);
|
|
92
|
+
}
|
|
93
|
+
else if (typeof value === 'object' && value !== null) {
|
|
94
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
sanitized[key] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return sanitized;
|
|
101
|
+
}
|
|
102
|
+
isSensitiveParameter(key) {
|
|
103
|
+
const sensitiveKeys = [
|
|
104
|
+
'token',
|
|
105
|
+
'access_token',
|
|
106
|
+
'api_key',
|
|
107
|
+
'password',
|
|
108
|
+
'secret',
|
|
109
|
+
'authorization',
|
|
110
|
+
'auth',
|
|
111
|
+
'key',
|
|
112
|
+
'credential',
|
|
113
|
+
];
|
|
114
|
+
return sensitiveKeys.some((sensitiveKey) => key.toLowerCase().includes(sensitiveKey));
|
|
115
|
+
}
|
|
116
|
+
sanitizeString(value) {
|
|
117
|
+
return value
|
|
118
|
+
.replace(/[a-zA-Z0-9]{30,}/g, '***')
|
|
119
|
+
.replace(/Bearer\s+[a-zA-Z0-9_-]+/gi, 'Bearer ***')
|
|
120
|
+
.replace(/token[s]?[:\s=]+[a-zA-Z0-9_-]+/gi, 'token=***')
|
|
121
|
+
.replace(/key[s]?[:\s=]+[a-zA-Z0-9_-]+/gi, 'key=***');
|
|
122
|
+
}
|
|
123
|
+
sanitizeObject(obj) {
|
|
124
|
+
if (Array.isArray(obj)) {
|
|
125
|
+
return obj.map((item) => typeof item === 'object' && item !== null ? this.sanitizeObject(item) : item);
|
|
126
|
+
}
|
|
127
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
128
|
+
const sanitized = {};
|
|
129
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
130
|
+
if (this.isSensitiveParameter(key)) {
|
|
131
|
+
sanitized[key] = '***';
|
|
132
|
+
}
|
|
133
|
+
else if (typeof value === 'string') {
|
|
134
|
+
sanitized[key] = this.sanitizeString(value);
|
|
135
|
+
}
|
|
136
|
+
else if (typeof value === 'object' && value !== null) {
|
|
137
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
sanitized[key] = value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return sanitized;
|
|
144
|
+
}
|
|
145
|
+
return obj;
|
|
146
|
+
}
|
|
147
|
+
sanitizeError(error) {
|
|
148
|
+
return this.sanitizeString(error);
|
|
149
|
+
}
|
|
150
|
+
outputLog(logEntry) {
|
|
151
|
+
const logMessage = this.formatLogMessage(logEntry);
|
|
152
|
+
if (logEntry.success) {
|
|
153
|
+
if (this.shouldLog('info')) {
|
|
154
|
+
console.error(`[INFO] ${logMessage}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (this.shouldLog('error')) {
|
|
159
|
+
console.error(`[ERROR] ${logMessage}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (logEntry.rateLimitInfo?.isLimited && this.shouldLog('warn')) {
|
|
163
|
+
console.error(`[WARN] Rate limit exceeded for ${logEntry.toolName}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
shouldLog(level) {
|
|
167
|
+
const levels = ['error', 'warn', 'info', 'debug'];
|
|
168
|
+
const currentLevelIndex = levels.indexOf(this.config.logLevel);
|
|
169
|
+
const requestedLevelIndex = levels.indexOf(level);
|
|
170
|
+
return requestedLevelIndex <= currentLevelIndex;
|
|
171
|
+
}
|
|
172
|
+
formatLogMessage(logEntry) {
|
|
173
|
+
const parts = [
|
|
174
|
+
`${logEntry.toolName}:${logEntry.operation}`,
|
|
175
|
+
logEntry.success ? 'SUCCESS' : 'FAILED',
|
|
176
|
+
];
|
|
177
|
+
if (logEntry.duration !== undefined) {
|
|
178
|
+
parts.push(`${logEntry.duration}ms`);
|
|
179
|
+
}
|
|
180
|
+
if (logEntry.rateLimitInfo) {
|
|
181
|
+
parts.push(`rate_limit_remaining:${logEntry.rateLimitInfo.remaining}`);
|
|
182
|
+
}
|
|
183
|
+
if (logEntry.error) {
|
|
184
|
+
parts.push(`error:"${logEntry.error}"`);
|
|
185
|
+
}
|
|
186
|
+
return parts.join(' | ');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export const globalRequestLogger = new RequestLogger();
|
|
190
|
+
export default globalRequestLogger;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type * as ynab from 'ynab';
|
|
2
|
+
interface ResponseFormatter {
|
|
3
|
+
format(data: unknown): string;
|
|
4
|
+
}
|
|
5
|
+
export type ResourceHandler = (uri: string, dependencies: ResourceDependencies) => Promise<{
|
|
6
|
+
contents: {
|
|
7
|
+
uri: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}[];
|
|
11
|
+
}>;
|
|
12
|
+
export interface ResourceDefinition {
|
|
13
|
+
uri: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ResourceDependencies {
|
|
19
|
+
ynabAPI: ynab.API;
|
|
20
|
+
responseFormatter: ResponseFormatter;
|
|
21
|
+
}
|
|
22
|
+
export declare class ResourceManager {
|
|
23
|
+
private dependencies;
|
|
24
|
+
private resourceHandlers;
|
|
25
|
+
private resourceDefinitions;
|
|
26
|
+
constructor(dependencies: ResourceDependencies);
|
|
27
|
+
registerResource(definition: ResourceDefinition, handler: ResourceHandler): void;
|
|
28
|
+
listResources(): {
|
|
29
|
+
resources: ResourceDefinition[];
|
|
30
|
+
};
|
|
31
|
+
readResource(uri: string): Promise<{
|
|
32
|
+
contents: {
|
|
33
|
+
uri: string;
|
|
34
|
+
mimeType: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const defaultResourceHandlers = {
|
|
2
|
+
'ynab://budgets': async (uri, { ynabAPI, responseFormatter }) => {
|
|
3
|
+
try {
|
|
4
|
+
const response = await ynabAPI.budgets.getBudgets();
|
|
5
|
+
const budgets = response.data.budgets.map((budget) => ({
|
|
6
|
+
id: budget.id,
|
|
7
|
+
name: budget.name,
|
|
8
|
+
last_modified_on: budget.last_modified_on,
|
|
9
|
+
first_month: budget.first_month,
|
|
10
|
+
last_month: budget.last_month,
|
|
11
|
+
currency_format: budget.currency_format,
|
|
12
|
+
}));
|
|
13
|
+
return {
|
|
14
|
+
contents: [
|
|
15
|
+
{
|
|
16
|
+
uri: uri,
|
|
17
|
+
mimeType: 'application/json',
|
|
18
|
+
text: responseFormatter.format({ budgets }),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`Failed to fetch budgets: ${error}`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
'ynab://user': async (uri, { ynabAPI, responseFormatter }) => {
|
|
28
|
+
try {
|
|
29
|
+
const response = await ynabAPI.user.getUser();
|
|
30
|
+
const userInfo = response.data.user;
|
|
31
|
+
const user = {
|
|
32
|
+
id: userInfo.id,
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
contents: [
|
|
36
|
+
{
|
|
37
|
+
uri: uri,
|
|
38
|
+
mimeType: 'application/json',
|
|
39
|
+
text: responseFormatter.format({ user }),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw new Error(`Failed to fetch user info: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const defaultResourceDefinitions = [
|
|
50
|
+
{
|
|
51
|
+
uri: 'ynab://budgets',
|
|
52
|
+
name: 'YNAB Budgets',
|
|
53
|
+
description: 'List of all available budgets',
|
|
54
|
+
mimeType: 'application/json',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
uri: 'ynab://user',
|
|
58
|
+
name: 'YNAB User Info',
|
|
59
|
+
description: 'Current user information including ID and email address',
|
|
60
|
+
mimeType: 'application/json',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
export class ResourceManager {
|
|
64
|
+
constructor(dependencies) {
|
|
65
|
+
this.dependencies = dependencies;
|
|
66
|
+
this.resourceHandlers = { ...defaultResourceHandlers };
|
|
67
|
+
this.resourceDefinitions = [...defaultResourceDefinitions];
|
|
68
|
+
}
|
|
69
|
+
registerResource(definition, handler) {
|
|
70
|
+
this.resourceDefinitions.push(definition);
|
|
71
|
+
this.resourceHandlers[definition.uri] = handler;
|
|
72
|
+
}
|
|
73
|
+
listResources() {
|
|
74
|
+
return {
|
|
75
|
+
resources: this.resourceDefinitions,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async readResource(uri) {
|
|
79
|
+
const handler = this.resourceHandlers[uri];
|
|
80
|
+
if (!handler) {
|
|
81
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
82
|
+
}
|
|
83
|
+
return await handler(uri, this.dependencies);
|
|
84
|
+
}
|
|
85
|
+
}
|