@dizzlkheinz/ynab-mcpb 0.18.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +87 -8
- package/bin/ynab-mcp-server.cjs +2 -2
- package/bin/ynab-mcp-server.js +3 -3
- package/biome.json +39 -0
- package/dist/bundle/index.cjs +67 -67
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -27
- package/dist/server/YNABMCPServer.d.ts +3 -4
- package/dist/server/YNABMCPServer.js +111 -116
- package/dist/server/budgetResolver.d.ts +6 -5
- package/dist/server/budgetResolver.js +46 -36
- package/dist/server/cacheKeys.js +6 -6
- package/dist/server/cacheManager.js +14 -11
- package/dist/server/completions.d.ts +2 -2
- package/dist/server/completions.js +20 -15
- package/dist/server/config.d.ts +10 -5
- package/dist/server/config.js +24 -7
- package/dist/server/deltaCache.d.ts +2 -2
- package/dist/server/deltaCache.js +22 -16
- package/dist/server/deltaCache.merge.d.ts +2 -2
- package/dist/server/diagnostics.d.ts +4 -4
- package/dist/server/diagnostics.js +38 -32
- package/dist/server/errorHandler.d.ts +5 -12
- package/dist/server/errorHandler.js +219 -217
- package/dist/server/prompts.d.ts +2 -2
- package/dist/server/prompts.js +45 -45
- package/dist/server/rateLimiter.js +4 -4
- package/dist/server/requestLogger.d.ts +1 -1
- package/dist/server/requestLogger.js +40 -35
- package/dist/server/resources.d.ts +3 -3
- package/dist/server/resources.js +55 -52
- package/dist/server/responseFormatter.js +6 -6
- package/dist/server/securityMiddleware.d.ts +2 -2
- package/dist/server/securityMiddleware.js +22 -20
- package/dist/server/serverKnowledgeStore.js +1 -1
- package/dist/server/toolRegistry.d.ts +3 -3
- package/dist/server/toolRegistry.js +47 -40
- package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
- package/dist/tools/__tests__/deltaTestUtils.js +2 -2
- package/dist/tools/accountTools.d.ts +9 -8
- package/dist/tools/accountTools.js +47 -47
- package/dist/tools/adapters.d.ts +13 -8
- package/dist/tools/adapters.js +21 -11
- package/dist/tools/budgetTools.d.ts +8 -7
- package/dist/tools/budgetTools.js +22 -22
- package/dist/tools/categoryTools.d.ts +9 -8
- package/dist/tools/categoryTools.js +68 -59
- package/dist/tools/compareTransactions/formatter.d.ts +3 -3
- package/dist/tools/compareTransactions/formatter.js +9 -9
- package/dist/tools/compareTransactions/index.d.ts +6 -6
- package/dist/tools/compareTransactions/index.js +58 -43
- package/dist/tools/compareTransactions/matcher.d.ts +1 -1
- package/dist/tools/compareTransactions/matcher.js +28 -15
- package/dist/tools/compareTransactions/parser.d.ts +2 -2
- package/dist/tools/compareTransactions/parser.js +144 -138
- package/dist/tools/compareTransactions/types.d.ts +4 -4
- package/dist/tools/compareTransactions.d.ts +1 -1
- package/dist/tools/compareTransactions.js +1 -1
- package/dist/tools/deltaFetcher.d.ts +2 -2
- package/dist/tools/deltaFetcher.js +16 -15
- package/dist/tools/deltaSupport.d.ts +4 -4
- package/dist/tools/deltaSupport.js +35 -41
- package/dist/tools/exportTransactions.d.ts +5 -4
- package/dist/tools/exportTransactions.js +61 -59
- package/dist/tools/monthTools.d.ts +7 -6
- package/dist/tools/monthTools.js +31 -29
- package/dist/tools/payeeTools.d.ts +7 -6
- package/dist/tools/payeeTools.js +28 -28
- package/dist/tools/reconcileAdapter.d.ts +2 -2
- package/dist/tools/reconcileAdapter.js +21 -11
- package/dist/tools/reconciliation/analyzer.d.ts +4 -4
- package/dist/tools/reconciliation/analyzer.js +136 -57
- package/dist/tools/reconciliation/csvParser.d.ts +3 -3
- package/dist/tools/reconciliation/csvParser.js +128 -104
- package/dist/tools/reconciliation/executor.d.ts +4 -4
- package/dist/tools/reconciliation/executor.js +148 -109
- package/dist/tools/reconciliation/index.d.ts +10 -10
- package/dist/tools/reconciliation/index.js +96 -83
- package/dist/tools/reconciliation/matcher.d.ts +3 -3
- package/dist/tools/reconciliation/matcher.js +17 -16
- package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
- package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
- package/dist/tools/reconciliation/recommendationEngine.js +40 -40
- package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
- package/dist/tools/reconciliation/reportFormatter.js +79 -54
- package/dist/tools/reconciliation/signDetector.d.ts +1 -1
- package/dist/tools/reconciliation/types.d.ts +19 -16
- package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
- package/dist/tools/schemas/common.d.ts +1 -1
- package/dist/tools/schemas/common.js +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
- package/dist/tools/schemas/outputs/index.d.ts +14 -14
- package/dist/tools/schemas/outputs/index.js +14 -14
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
- package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
- package/dist/tools/schemas/shared/commonOutputs.js +15 -9
- package/dist/tools/transactionReadTools.d.ts +11 -0
- package/dist/tools/transactionReadTools.js +202 -0
- package/dist/tools/transactionSchemas.d.ts +309 -0
- package/dist/tools/transactionSchemas.js +235 -0
- package/dist/tools/transactionTools.d.ts +6 -302
- package/dist/tools/transactionTools.js +7 -2054
- package/dist/tools/transactionUtils.d.ts +31 -0
- package/dist/tools/transactionUtils.js +364 -0
- package/dist/tools/transactionWriteTools.d.ts +20 -0
- package/dist/tools/transactionWriteTools.js +1342 -0
- package/dist/tools/utilityTools.d.ts +5 -4
- package/dist/tools/utilityTools.js +11 -11
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +6 -6
- package/dist/types/reconciliation.d.ts +1 -1
- package/dist/types/toolRegistration.d.ts +14 -12
- package/dist/utils/amountUtils.js +1 -1
- package/dist/utils/dateUtils.js +4 -4
- package/dist/utils/errors.d.ts +3 -3
- package/dist/utils/errors.js +4 -4
- package/dist/utils/money.d.ts +2 -2
- package/dist/utils/money.js +8 -8
- package/dist/utils/validationError.d.ts +1 -1
- package/dist/utils/validationError.js +1 -1
- package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
- package/docs/assets/schemas/reconciliation-v2.json +360 -336
- package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
- package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
- package/esbuild.config.mjs +53 -50
- package/meta.json +12548 -12548
- package/package.json +98 -109
- package/scripts/analyze-bundle.mjs +33 -30
- package/scripts/create-pr-description.js +169 -120
- package/scripts/run-all-tests.js +205 -0
- package/scripts/run-domain-integration-tests.js +28 -18
- package/scripts/run-generate-mcpb.js +19 -17
- package/scripts/run-throttled-integration-tests.js +92 -83
- package/scripts/test-delta-params.mjs +149 -120
- package/scripts/test-recommendations.ts +36 -32
- package/scripts/tmpTransaction.ts +80 -43
- package/scripts/validate-env.js +98 -91
- package/scripts/verify-build.js +78 -76
- package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
- package/src/__tests__/performance.test.ts +723 -671
- package/src/__tests__/setup.ts +442 -395
- package/src/__tests__/smoke.e2e.test.ts +41 -39
- package/src/__tests__/testRunner.ts +314 -295
- package/src/__tests__/testUtils.ts +456 -364
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
- package/src/index.ts +68 -59
- package/src/server/CLAUDE.md +480 -0
- package/src/server/YNABMCPServer.ts +821 -794
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
- package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
- package/src/server/__tests__/budgetResolver.test.ts +466 -423
- package/src/server/__tests__/cacheManager.test.ts +891 -874
- package/src/server/__tests__/completions.integration.test.ts +115 -106
- package/src/server/__tests__/completions.test.ts +334 -313
- package/src/server/__tests__/config.test.ts +98 -86
- package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
- package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
- package/src/server/__tests__/deltaCache.test.ts +946 -759
- package/src/server/__tests__/diagnostics.test.ts +825 -792
- package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
- package/src/server/__tests__/errorHandler.test.ts +402 -397
- package/src/server/__tests__/prompts.test.ts +424 -347
- package/src/server/__tests__/rateLimiter.test.ts +313 -309
- package/src/server/__tests__/requestLogger.test.ts +443 -403
- package/src/server/__tests__/resources.template.test.ts +196 -185
- package/src/server/__tests__/resources.test.ts +294 -288
- package/src/server/__tests__/security.integration.test.ts +487 -421
- package/src/server/__tests__/securityMiddleware.test.ts +519 -444
- package/src/server/__tests__/server-startup.integration.test.ts +509 -490
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
- package/src/server/__tests__/toolRegistration.test.ts +239 -210
- package/src/server/__tests__/toolRegistry.test.ts +907 -845
- package/src/server/budgetResolver.ts +221 -181
- package/src/server/cacheKeys.ts +6 -6
- package/src/server/cacheManager.ts +498 -484
- package/src/server/completions.ts +267 -243
- package/src/server/config.ts +35 -14
- package/src/server/deltaCache.merge.ts +146 -128
- package/src/server/deltaCache.ts +352 -309
- package/src/server/diagnostics.ts +257 -242
- package/src/server/errorHandler.ts +747 -744
- package/src/server/prompts.ts +181 -176
- package/src/server/rateLimiter.ts +131 -129
- package/src/server/requestLogger.ts +350 -322
- package/src/server/resources.ts +442 -374
- package/src/server/responseFormatter.ts +41 -37
- package/src/server/securityMiddleware.ts +223 -205
- package/src/server/serverKnowledgeStore.ts +67 -67
- package/src/server/toolRegistry.ts +508 -474
- package/src/tools/CLAUDE.md +604 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
- package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
- package/src/tools/__tests__/accountTools.test.ts +685 -638
- package/src/tools/__tests__/adapters.test.ts +142 -108
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
- package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
- package/src/tools/__tests__/budgetTools.test.ts +442 -413
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
- package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
- package/src/tools/__tests__/categoryTools.test.ts +656 -625
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
- package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
- package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
- package/src/tools/__tests__/compareTransactions.test.ts +352 -332
- package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
- package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
- package/src/tools/__tests__/deltaSupport.test.ts +211 -184
- package/src/tools/__tests__/deltaTestUtils.ts +37 -33
- package/src/tools/__tests__/exportTransactions.test.ts +205 -200
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
- package/src/tools/__tests__/monthTools.test.ts +561 -512
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
- package/src/tools/__tests__/payeeTools.test.ts +486 -434
- package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
- package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
- package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
- package/src/tools/__tests__/utilityTools.test.ts +68 -58
- package/src/tools/accountTools.ts +293 -271
- package/src/tools/adapters.ts +120 -63
- package/src/tools/budgetTools.ts +121 -116
- package/src/tools/categoryTools.ts +379 -339
- package/src/tools/compareTransactions/formatter.ts +131 -119
- package/src/tools/compareTransactions/index.ts +249 -214
- package/src/tools/compareTransactions/matcher.ts +259 -209
- package/src/tools/compareTransactions/parser.ts +517 -487
- package/src/tools/compareTransactions/types.ts +38 -38
- package/src/tools/compareTransactions.ts +1 -1
- package/src/tools/deltaFetcher.ts +281 -260
- package/src/tools/deltaSupport.ts +264 -259
- package/src/tools/exportTransactions.ts +230 -218
- package/src/tools/monthTools.ts +180 -165
- package/src/tools/payeeTools.ts +152 -140
- package/src/tools/reconcileAdapter.ts +297 -246
- package/src/tools/reconciliation/CLAUDE.md +506 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
- package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
- package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
- package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
- package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
- package/src/tools/reconciliation/analyzer.ts +582 -406
- package/src/tools/reconciliation/csvParser.ts +656 -609
- package/src/tools/reconciliation/executor.ts +1290 -1128
- package/src/tools/reconciliation/index.ts +580 -528
- package/src/tools/reconciliation/matcher.ts +256 -240
- package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
- package/src/tools/reconciliation/recommendationEngine.ts +357 -345
- package/src/tools/reconciliation/reportFormatter.ts +349 -276
- package/src/tools/reconciliation/signDetector.ts +89 -83
- package/src/tools/reconciliation/types.ts +164 -153
- package/src/tools/reconciliation/ynabAdapter.ts +17 -15
- package/src/tools/schemas/CLAUDE.md +546 -0
- package/src/tools/schemas/common.ts +1 -1
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
- package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
- package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
- package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
- package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
- package/src/tools/schemas/outputs/index.ts +163 -163
- package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
- package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
- package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
- package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
- package/src/tools/schemas/shared/commonOutputs.ts +27 -19
- package/src/tools/toolCategories.ts +114 -114
- package/src/tools/transactionReadTools.ts +327 -0
- package/src/tools/transactionSchemas.ts +484 -0
- package/src/tools/transactionTools.ts +107 -2990
- package/src/tools/transactionUtils.ts +621 -0
- package/src/tools/transactionWriteTools.ts +2110 -0
- package/src/tools/utilityTools.ts +46 -41
- package/src/types/CLAUDE.md +477 -0
- package/src/types/__tests__/index.test.ts +51 -51
- package/src/types/index.ts +43 -39
- package/src/types/integration-tests.d.ts +26 -26
- package/src/types/reconciliation.ts +29 -29
- package/src/types/toolAnnotations.ts +30 -30
- package/src/types/toolRegistration.ts +43 -32
- package/src/utils/CLAUDE.md +508 -0
- package/src/utils/__tests__/dateUtils.test.ts +174 -168
- package/src/utils/__tests__/money.test.ts +193 -187
- package/src/utils/amountUtils.ts +5 -5
- package/src/utils/baseError.ts +5 -5
- package/src/utils/dateUtils.ts +29 -26
- package/src/utils/errors.ts +14 -14
- package/src/utils/money.ts +66 -52
- package/src/utils/validationError.ts +1 -1
- package/tsconfig.json +29 -29
- package/tsconfig.prod.json +16 -16
- package/vitest-reporters/split-json-reporter.ts +247 -204
- package/vitest.config.ts +99 -95
- package/.prettierignore +0 -10
- package/.prettierrc.json +0 -10
- package/eslint.config.js +0 -49
|
@@ -1,90 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createErrorHandler } from "./errorHandler.js";
|
|
2
|
+
const fallbackErrorHandler = createErrorHandler({
|
|
3
|
+
format: (value) => JSON.stringify(value, null, 2),
|
|
4
|
+
});
|
|
2
5
|
export class BudgetResolver {
|
|
3
|
-
static resolveBudgetId(providedId, defaultId) {
|
|
6
|
+
static resolveBudgetId(providedId, defaultId, errorHandler) {
|
|
4
7
|
if (providedId !== undefined && providedId !== null) {
|
|
5
8
|
const trimmed = providedId.trim();
|
|
6
|
-
if (trimmed ===
|
|
9
|
+
if (trimmed === "default") {
|
|
7
10
|
if (defaultId) {
|
|
8
|
-
return
|
|
11
|
+
return BudgetResolver.validateBudgetId(defaultId, errorHandler);
|
|
9
12
|
}
|
|
10
|
-
return
|
|
13
|
+
return BudgetResolver.createMissingBudgetError(errorHandler);
|
|
11
14
|
}
|
|
12
|
-
if (trimmed ===
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
if (trimmed === "last-used") {
|
|
16
|
+
const eh = errorHandler ?? fallbackErrorHandler;
|
|
17
|
+
return eh.createValidationError("Unsupported keyword", 'The "last-used" keyword is not supported yet. Please use a specific budget ID or set a default budget.', [
|
|
18
|
+
"Use a specific budget ID (UUID format)",
|
|
19
|
+
"Set a default budget using the set_default_budget tool",
|
|
16
20
|
'Use the "default" keyword after setting a default budget',
|
|
17
|
-
|
|
21
|
+
"Run the list_budgets tool to see available budget IDs",
|
|
18
22
|
]);
|
|
19
23
|
}
|
|
20
|
-
return
|
|
24
|
+
return BudgetResolver.validateBudgetId(providedId, errorHandler);
|
|
21
25
|
}
|
|
22
26
|
if (defaultId) {
|
|
23
|
-
return
|
|
27
|
+
return BudgetResolver.validateBudgetId(defaultId, errorHandler);
|
|
24
28
|
}
|
|
25
|
-
return
|
|
29
|
+
return BudgetResolver.createMissingBudgetError(errorHandler);
|
|
26
30
|
}
|
|
27
|
-
static validateBudgetId(budgetId) {
|
|
28
|
-
if (!budgetId || typeof budgetId !==
|
|
29
|
-
return
|
|
31
|
+
static validateBudgetId(budgetId, errorHandler) {
|
|
32
|
+
if (!budgetId || typeof budgetId !== "string") {
|
|
33
|
+
return BudgetResolver.createInvalidBudgetError("Budget ID must be provided as a non-empty string", errorHandler);
|
|
30
34
|
}
|
|
31
35
|
const trimmed = budgetId.trim();
|
|
32
36
|
if (!trimmed) {
|
|
33
|
-
return
|
|
37
|
+
return BudgetResolver.createInvalidBudgetError("Budget ID cannot be empty or whitespace only", errorHandler);
|
|
34
38
|
}
|
|
35
|
-
if (process.env[
|
|
39
|
+
if (process.env["NODE_ENV"] === "test") {
|
|
36
40
|
const testIdentifierPattern = /^(test|budget|account|category|transaction|payee|mock)-[a-z0-9_-]+$/i;
|
|
37
41
|
if (testIdentifierPattern.test(trimmed)) {
|
|
38
42
|
return trimmed;
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
|
-
if (!
|
|
42
|
-
return
|
|
45
|
+
if (!BudgetResolver.UUID_REGEX.test(trimmed)) {
|
|
46
|
+
return BudgetResolver.createInvalidBudgetError(`Invalid budget ID format: '${trimmed}'. Must be a valid UUID format (versions 1-5)`, errorHandler);
|
|
43
47
|
}
|
|
44
48
|
return trimmed;
|
|
45
49
|
}
|
|
46
|
-
static createMissingBudgetError() {
|
|
50
|
+
static createMissingBudgetError(errorHandler) {
|
|
47
51
|
const detailMessage = `A budget ID is required for this operation. You can either:
|
|
48
52
|
1. Provide a specific budget_id parameter
|
|
49
53
|
2. Set a default budget using the set_default_budget tool first`;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
const eh = errorHandler ?? fallbackErrorHandler;
|
|
55
|
+
return eh.createValidationError("No budget ID provided and no default budget set", detailMessage, [
|
|
56
|
+
"Set a default budget first using the set_default_budget tool",
|
|
57
|
+
"Provide a budget_id parameter when invoking the tool",
|
|
53
58
|
]);
|
|
54
59
|
}
|
|
55
|
-
static createInvalidBudgetError(details) {
|
|
60
|
+
static createInvalidBudgetError(details, errorHandler) {
|
|
56
61
|
const detailMessage = `${details}
|
|
57
62
|
|
|
58
63
|
Valid formats:
|
|
59
64
|
- UUID format (versions 1-5, e.g., "123e4567-e89b-12d3-a456-426614174000")
|
|
60
|
-
- Special keywords: ${
|
|
65
|
+
- Special keywords: ${BudgetResolver.ALLOWED_KEYWORDS.map((k) => `"${k}"`).join(", ")}
|
|
61
66
|
|
|
62
67
|
You can use the list_budgets tool to see available budget IDs.`;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
const eh = errorHandler ?? fallbackErrorHandler;
|
|
69
|
+
return eh.createValidationError("Invalid budget ID format", detailMessage, [
|
|
70
|
+
"Use a valid UUID format (UUID v1-v5, e.g., 123e4567-e89b-12d3-a456-426614174000; standard UUID v4 format works as well)",
|
|
71
|
+
"Run the list_budgets tool to view available budget IDs",
|
|
66
72
|
'Use the special keyword "default" for convenience',
|
|
67
73
|
]);
|
|
68
74
|
}
|
|
69
75
|
static resolveBudgetIdOrThrow(providedId, defaultId) {
|
|
70
|
-
const result =
|
|
71
|
-
if (typeof result ===
|
|
76
|
+
const result = BudgetResolver.resolveBudgetId(providedId, defaultId);
|
|
77
|
+
if (typeof result === "string") {
|
|
72
78
|
return result;
|
|
73
79
|
}
|
|
74
|
-
const errorText = result.content?.[0]?.type ===
|
|
80
|
+
const errorText = result.content?.[0]?.type === "text"
|
|
81
|
+
? result.content[0].text
|
|
82
|
+
: "Budget resolution failed";
|
|
75
83
|
throw new Error(errorText);
|
|
76
84
|
}
|
|
77
85
|
static validateBudgetIdOrThrow(budgetId) {
|
|
78
|
-
const result =
|
|
79
|
-
if (typeof result ===
|
|
86
|
+
const result = BudgetResolver.validateBudgetId(budgetId);
|
|
87
|
+
if (typeof result === "string") {
|
|
80
88
|
return result;
|
|
81
89
|
}
|
|
82
|
-
const errorText = result.content?.[0]?.type ===
|
|
90
|
+
const errorText = result.content?.[0]?.type === "text"
|
|
91
|
+
? result.content[0].text
|
|
92
|
+
: "Budget validation failed";
|
|
83
93
|
throw new Error(errorText);
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
BudgetResolver.UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
87
|
-
BudgetResolver.ALLOWED_KEYWORDS = [
|
|
97
|
+
BudgetResolver.ALLOWED_KEYWORDS = ["default"];
|
|
88
98
|
export function resolveBudgetId(providedId, defaultId) {
|
|
89
99
|
return BudgetResolver.resolveBudgetId(providedId, defaultId);
|
|
90
100
|
}
|
package/dist/server/cacheKeys.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const CacheKeys = {
|
|
2
|
-
ACCOUNTS:
|
|
3
|
-
BUDGETS:
|
|
4
|
-
CATEGORIES:
|
|
5
|
-
PAYEES:
|
|
6
|
-
TRANSACTIONS:
|
|
7
|
-
MONTHS:
|
|
2
|
+
ACCOUNTS: "accounts",
|
|
3
|
+
BUDGETS: "budgets",
|
|
4
|
+
CATEGORIES: "categories",
|
|
5
|
+
PAYEES: "payees",
|
|
6
|
+
TRANSACTIONS: "transactions",
|
|
7
|
+
MONTHS: "months",
|
|
8
8
|
};
|
|
@@ -7,9 +7,9 @@ export class CacheManager {
|
|
|
7
7
|
this.lastCleanup = null;
|
|
8
8
|
this.pendingFetches = new Map();
|
|
9
9
|
this.pendingRefresh = new Set();
|
|
10
|
-
this.maxEntries = this.parseEnvInt(
|
|
11
|
-
this.defaultStaleWindow = this.parseEnvInt(
|
|
12
|
-
this.defaultTTL = this.parseEnvInt(
|
|
10
|
+
this.maxEntries = this.parseEnvInt("YNAB_MCP_CACHE_MAX_ENTRIES", 1000);
|
|
11
|
+
this.defaultStaleWindow = this.parseEnvInt("YNAB_MCP_CACHE_STALE_MS", 2 * 60 * 1000);
|
|
12
|
+
this.defaultTTL = this.parseEnvInt("YNAB_MCP_CACHE_DEFAULT_TTL_MS", 300000);
|
|
13
13
|
}
|
|
14
14
|
get(key) {
|
|
15
15
|
const entry = this.cache.get(key);
|
|
@@ -68,7 +68,7 @@ export class CacheManager {
|
|
|
68
68
|
}
|
|
69
69
|
let ttl;
|
|
70
70
|
let staleWhileRevalidate;
|
|
71
|
-
if (typeof ttlOrOptions ===
|
|
71
|
+
if (typeof ttlOrOptions === "number") {
|
|
72
72
|
ttl = Number.isFinite(ttlOrOptions) ? ttlOrOptions : this.defaultTTL;
|
|
73
73
|
staleWhileRevalidate = undefined;
|
|
74
74
|
}
|
|
@@ -79,7 +79,7 @@ export class CacheManager {
|
|
|
79
79
|
else {
|
|
80
80
|
const providedTtl = ttlOrOptions?.ttl;
|
|
81
81
|
ttl = providedTtl !== undefined ? providedTtl : this.defaultTTL;
|
|
82
|
-
const hasStaleWhileRevalidate = ttlOrOptions !== undefined &&
|
|
82
|
+
const hasStaleWhileRevalidate = ttlOrOptions !== undefined && "staleWhileRevalidate" in ttlOrOptions;
|
|
83
83
|
if (hasStaleWhileRevalidate) {
|
|
84
84
|
staleWhileRevalidate = ttlOrOptions.staleWhileRevalidate;
|
|
85
85
|
if (staleWhileRevalidate === undefined && this.defaultStaleWindow > 0) {
|
|
@@ -124,7 +124,9 @@ export class CacheManager {
|
|
|
124
124
|
if (!prefix) {
|
|
125
125
|
return 0;
|
|
126
126
|
}
|
|
127
|
-
const normalizedPrefix = prefix.endsWith(
|
|
127
|
+
const normalizedPrefix = prefix.endsWith(":")
|
|
128
|
+
? prefix.slice(0, -1)
|
|
129
|
+
: prefix;
|
|
128
130
|
const prefixWithColon = `${normalizedPrefix}:`;
|
|
129
131
|
let removed = 0;
|
|
130
132
|
for (const key of this.cache.keys()) {
|
|
@@ -143,7 +145,7 @@ export class CacheManager {
|
|
|
143
145
|
}
|
|
144
146
|
let removed = 0;
|
|
145
147
|
for (const key of this.cache.keys()) {
|
|
146
|
-
const segments = key.split(
|
|
148
|
+
const segments = key.split(":");
|
|
147
149
|
if (segments.some((segment) => segment === budgetId)) {
|
|
148
150
|
this.cache.delete(key);
|
|
149
151
|
this.pendingFetches.delete(key);
|
|
@@ -229,7 +231,8 @@ export class CacheManager {
|
|
|
229
231
|
if (ttl !== undefined) {
|
|
230
232
|
refreshOptions.ttl = ttl;
|
|
231
233
|
}
|
|
232
|
-
const staleWhileRevalidate = options.staleWhileRevalidate ??
|
|
234
|
+
const staleWhileRevalidate = options.staleWhileRevalidate ??
|
|
235
|
+
existingEntry?.staleWhileRevalidate;
|
|
233
236
|
if (staleWhileRevalidate !== undefined) {
|
|
234
237
|
refreshOptions.staleWhileRevalidate = staleWhileRevalidate;
|
|
235
238
|
}
|
|
@@ -283,14 +286,14 @@ export class CacheManager {
|
|
|
283
286
|
const value = process.env[key];
|
|
284
287
|
if (!value)
|
|
285
288
|
return defaultValue;
|
|
286
|
-
const parsed = parseInt(value, 10);
|
|
287
|
-
return isNaN(parsed) ? defaultValue : parsed;
|
|
289
|
+
const parsed = Number.parseInt(value, 10);
|
|
290
|
+
return Number.isNaN(parsed) ? defaultValue : parsed;
|
|
288
291
|
}
|
|
289
292
|
static generateKey(prefix, ...params) {
|
|
290
293
|
const cleanParams = params
|
|
291
294
|
.filter((p) => p !== undefined)
|
|
292
295
|
.map((p) => String(p))
|
|
293
|
-
.join(
|
|
296
|
+
.join(":");
|
|
294
297
|
return `${prefix}:${cleanParams}`;
|
|
295
298
|
}
|
|
296
299
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CACHE_TTLS } from
|
|
1
|
+
import { CACHE_TTLS } from "./cacheManager.js";
|
|
2
2
|
const MAX_COMPLETIONS = 100;
|
|
3
3
|
export class CompletionsManager {
|
|
4
4
|
constructor(ynabAPI, cacheManager, getDefaultBudgetId) {
|
|
@@ -9,23 +9,23 @@ export class CompletionsManager {
|
|
|
9
9
|
async getCompletions(argumentName, value, context) {
|
|
10
10
|
const normalizedName = argumentName.toLowerCase();
|
|
11
11
|
switch (normalizedName) {
|
|
12
|
-
case
|
|
12
|
+
case "budget_id":
|
|
13
13
|
return this.completeBudgets(value);
|
|
14
|
-
case
|
|
15
|
-
case
|
|
14
|
+
case "account_id":
|
|
15
|
+
case "account_name":
|
|
16
16
|
return this.completeAccounts(value, context);
|
|
17
|
-
case
|
|
18
|
-
case
|
|
17
|
+
case "category":
|
|
18
|
+
case "category_id":
|
|
19
19
|
return this.completeCategories(value, context);
|
|
20
|
-
case
|
|
21
|
-
case
|
|
20
|
+
case "payee":
|
|
21
|
+
case "payee_id":
|
|
22
22
|
return this.completePayees(value, context);
|
|
23
23
|
default:
|
|
24
24
|
return { completion: { values: [], total: 0, hasMore: false } };
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
async completeBudgets(value) {
|
|
28
|
-
const budgets = await this.cacheManager.wrap(
|
|
28
|
+
const budgets = await this.cacheManager.wrap("completions:budgets", {
|
|
29
29
|
ttl: CACHE_TTLS.BUDGETS,
|
|
30
30
|
loader: async () => {
|
|
31
31
|
const response = await this.ynabAPI.budgets.getBudgets();
|
|
@@ -38,7 +38,7 @@ export class CompletionsManager {
|
|
|
38
38
|
return this.filterAndFormat(budgets, value, (b) => [b.name, b.id]);
|
|
39
39
|
}
|
|
40
40
|
async completeAccounts(value, context) {
|
|
41
|
-
const budgetId = context?.arguments?.[
|
|
41
|
+
const budgetId = context?.arguments?.["budget_id"] ?? this.getDefaultBudgetId();
|
|
42
42
|
if (!budgetId) {
|
|
43
43
|
return { completion: { values: [], total: 0, hasMore: false } };
|
|
44
44
|
}
|
|
@@ -57,7 +57,7 @@ export class CompletionsManager {
|
|
|
57
57
|
return this.filterAndFormat(accounts, value, (a) => [a.name, a.id]);
|
|
58
58
|
}
|
|
59
59
|
async completeCategories(value, context) {
|
|
60
|
-
const budgetId = context?.arguments?.[
|
|
60
|
+
const budgetId = context?.arguments?.["budget_id"] ?? this.getDefaultBudgetId();
|
|
61
61
|
if (!budgetId) {
|
|
62
62
|
return { completion: { values: [], total: 0, hasMore: false } };
|
|
63
63
|
}
|
|
@@ -82,10 +82,14 @@ export class CompletionsManager {
|
|
|
82
82
|
return result;
|
|
83
83
|
},
|
|
84
84
|
});
|
|
85
|
-
return this.filterAndFormat(categories, value, (c) => [
|
|
85
|
+
return this.filterAndFormat(categories, value, (c) => [
|
|
86
|
+
c.name,
|
|
87
|
+
`${c.group}: ${c.name}`,
|
|
88
|
+
c.id,
|
|
89
|
+
]);
|
|
86
90
|
}
|
|
87
91
|
async completePayees(value, context) {
|
|
88
|
-
const budgetId = context?.arguments?.[
|
|
92
|
+
const budgetId = context?.arguments?.["budget_id"] ?? this.getDefaultBudgetId();
|
|
89
93
|
if (!budgetId) {
|
|
90
94
|
return { completion: { values: [], total: 0, hasMore: false } };
|
|
91
95
|
}
|
|
@@ -128,7 +132,7 @@ export class CompletionsManager {
|
|
|
128
132
|
return -1;
|
|
129
133
|
if (!aStartsWith && bStartsWith)
|
|
130
134
|
return 1;
|
|
131
|
-
return (aCache.values[0] ??
|
|
135
|
+
return (aCache.values[0] ?? "").localeCompare(bCache.values[0] ?? "");
|
|
132
136
|
});
|
|
133
137
|
const uniqueValues = new Set();
|
|
134
138
|
for (const item of matches) {
|
|
@@ -136,7 +140,8 @@ export class CompletionsManager {
|
|
|
136
140
|
let selectedValue;
|
|
137
141
|
for (let i = 0; i < values.length; i++) {
|
|
138
142
|
if (lowerValues[i]?.includes(lowerValue)) {
|
|
139
|
-
if (selectedValue === undefined ||
|
|
143
|
+
if (selectedValue === undefined ||
|
|
144
|
+
i < values.indexOf(selectedValue)) {
|
|
140
145
|
selectedValue = values[i];
|
|
141
146
|
}
|
|
142
147
|
break;
|
package/dist/server/config.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { z } from
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { z } from "zod";
|
|
3
3
|
declare const envSchema: z.ZodObject<{
|
|
4
|
-
YNAB_ACCESS_TOKEN: z.ZodString
|
|
4
|
+
YNAB_ACCESS_TOKEN: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodString>;
|
|
5
5
|
YNAB_DEFAULT_BUDGET_ID: z.ZodOptional<z.ZodString>;
|
|
6
|
+
YNAB_MCP_ENABLE_DELTA: z.ZodOptional<z.ZodEnum<{
|
|
7
|
+
true: "true";
|
|
8
|
+
false: "false";
|
|
9
|
+
}>>;
|
|
6
10
|
MCP_PORT: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
7
11
|
LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
|
|
8
|
-
debug: "debug";
|
|
9
12
|
error: "error";
|
|
10
13
|
warn: "warn";
|
|
11
14
|
info: "info";
|
|
15
|
+
debug: "debug";
|
|
12
16
|
trace: "trace";
|
|
13
17
|
fatal: "fatal";
|
|
14
18
|
}>>;
|
|
@@ -17,8 +21,9 @@ export type AppConfig = z.infer<typeof envSchema>;
|
|
|
17
21
|
export declare function loadConfig(env?: NodeJS.ProcessEnv): AppConfig;
|
|
18
22
|
export declare const config: {
|
|
19
23
|
YNAB_ACCESS_TOKEN: string;
|
|
20
|
-
LOG_LEVEL: "
|
|
24
|
+
LOG_LEVEL: "error" | "warn" | "info" | "debug" | "trace" | "fatal";
|
|
21
25
|
YNAB_DEFAULT_BUDGET_ID?: string | undefined;
|
|
26
|
+
YNAB_MCP_ENABLE_DELTA?: "true" | "false" | undefined;
|
|
22
27
|
MCP_PORT?: number | undefined;
|
|
23
28
|
};
|
|
24
29
|
export {};
|
package/dist/server/config.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { z } from
|
|
3
|
-
import { fromZodError } from
|
|
4
|
-
import { ValidationError } from
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { fromZodError } from "zod-validation-error";
|
|
4
|
+
import { ValidationError } from "../utils/errors.js";
|
|
5
|
+
const normalizeEnvValue = (value) => {
|
|
6
|
+
if (typeof value !== "string") {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
const lowered = trimmed.toLowerCase();
|
|
11
|
+
if (lowered === "undefined" || lowered === "null") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
};
|
|
5
16
|
const envSchema = z.object({
|
|
6
|
-
YNAB_ACCESS_TOKEN: z.
|
|
7
|
-
YNAB_DEFAULT_BUDGET_ID: z
|
|
17
|
+
YNAB_ACCESS_TOKEN: z.preprocess(normalizeEnvValue, z.string().min(1, "YNAB_ACCESS_TOKEN must be a non-empty string")),
|
|
18
|
+
YNAB_DEFAULT_BUDGET_ID: z
|
|
19
|
+
.string()
|
|
20
|
+
.uuid("YNAB_DEFAULT_BUDGET_ID must be a valid UUID")
|
|
21
|
+
.optional(),
|
|
22
|
+
YNAB_MCP_ENABLE_DELTA: z.enum(["true", "false"]).optional(),
|
|
8
23
|
MCP_PORT: z.coerce.number().int().positive().optional(),
|
|
9
|
-
LOG_LEVEL: z
|
|
24
|
+
LOG_LEVEL: z
|
|
25
|
+
.enum(["trace", "debug", "info", "warn", "error", "fatal"])
|
|
26
|
+
.default("info"),
|
|
10
27
|
});
|
|
11
28
|
export function loadConfig(env = process.env) {
|
|
12
29
|
const result = envSchema.safeParse(env);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CacheManager } from
|
|
2
|
-
import { ServerKnowledgeStore } from
|
|
1
|
+
import type { CacheManager } from "./cacheManager.js";
|
|
2
|
+
import type { ServerKnowledgeStore } from "./serverKnowledgeStore.js";
|
|
3
3
|
export interface Logger {
|
|
4
4
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
5
5
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import globalRequestLogger from
|
|
1
|
+
import globalRequestLogger from "./requestLogger.js";
|
|
2
2
|
const LARGE_KNOWLEDGE_GAP_THRESHOLD = 100;
|
|
3
3
|
const requestLoggerAdapter = {
|
|
4
4
|
info(message, meta) {
|
|
5
|
-
globalRequestLogger.logSuccess(
|
|
5
|
+
globalRequestLogger.logSuccess("delta-cache", message, meta ? { ...meta } : {});
|
|
6
6
|
},
|
|
7
7
|
warn(message, meta) {
|
|
8
8
|
const parameters = {
|
|
9
9
|
...(meta ? { ...meta } : {}),
|
|
10
|
-
severity:
|
|
10
|
+
severity: "warn",
|
|
11
11
|
};
|
|
12
|
-
globalRequestLogger.logSuccess(
|
|
12
|
+
globalRequestLogger.logSuccess("delta-cache", message, parameters);
|
|
13
13
|
},
|
|
14
14
|
error(message, meta) {
|
|
15
15
|
const parameters = meta ? { ...meta } : {};
|
|
16
|
-
const errorField = parameters[
|
|
17
|
-
const errorDetail = typeof errorField ===
|
|
18
|
-
globalRequestLogger.logError(
|
|
16
|
+
const errorField = parameters["error"];
|
|
17
|
+
const errorDetail = typeof errorField === "string" ? errorField : message;
|
|
18
|
+
globalRequestLogger.logError("delta-cache", message, parameters, errorDetail);
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
export class DeltaCache {
|
|
@@ -29,7 +29,7 @@ export class DeltaCache {
|
|
|
29
29
|
this.knowledgeGapEvents = 0;
|
|
30
30
|
}
|
|
31
31
|
async fetchWithDelta(cacheKey, budgetId, fetcher, merger, options) {
|
|
32
|
-
const effectiveTtl = this.assertFiniteTtl(
|
|
32
|
+
const effectiveTtl = this.assertFiniteTtl("fetchWithDelta", cacheKey, options.ttl);
|
|
33
33
|
if (!this.isDeltaEnabled()) {
|
|
34
34
|
return this.fetchWithoutDelta(cacheKey, budgetId, fetcher, options);
|
|
35
35
|
}
|
|
@@ -46,22 +46,26 @@ export class DeltaCache {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
const lastKnowledge = options.forceFullRefresh
|
|
49
|
+
const lastKnowledge = options.forceFullRefresh
|
|
50
|
+
? undefined
|
|
51
|
+
: this.knowledgeStore.get(cacheKey);
|
|
50
52
|
const canUseDelta = Boolean(!options.forceFullRefresh && cachedEntry && lastKnowledge !== undefined);
|
|
51
53
|
const requestedKnowledge = canUseDelta ? lastKnowledge : undefined;
|
|
52
54
|
let response = await fetcher(requestedKnowledge);
|
|
53
|
-
const knowledgeGap = requestedKnowledge !== undefined
|
|
55
|
+
const knowledgeGap = requestedKnowledge !== undefined
|
|
56
|
+
? response.serverKnowledge - requestedKnowledge
|
|
57
|
+
: 0;
|
|
54
58
|
let forcedFullRefreshDueToGap = false;
|
|
55
59
|
if (knowledgeGap > LARGE_KNOWLEDGE_GAP_THRESHOLD) {
|
|
56
|
-
this.logger.warn(
|
|
60
|
+
this.logger.warn("delta-cache.knowledge-gap", {
|
|
57
61
|
budgetId,
|
|
58
62
|
cacheKey,
|
|
59
63
|
lastKnowledge: requestedKnowledge,
|
|
60
64
|
serverKnowledge: response.serverKnowledge,
|
|
61
65
|
gap: knowledgeGap,
|
|
62
66
|
threshold: LARGE_KNOWLEDGE_GAP_THRESHOLD,
|
|
63
|
-
action:
|
|
64
|
-
recommendation:
|
|
67
|
+
action: "full-refresh",
|
|
68
|
+
recommendation: "Consider forcing a full refresh to resync cache.",
|
|
65
69
|
});
|
|
66
70
|
forcedFullRefreshDueToGap = true;
|
|
67
71
|
this.knowledgeGapEvents++;
|
|
@@ -77,7 +81,9 @@ export class DeltaCache {
|
|
|
77
81
|
finalSnapshot = merger(cachedEntry.snapshot, response.data, options.mergeOptions);
|
|
78
82
|
usedDelta = true;
|
|
79
83
|
}
|
|
80
|
-
else if (cachedEntry &&
|
|
84
|
+
else if (cachedEntry &&
|
|
85
|
+
requestedKnowledge !== undefined &&
|
|
86
|
+
!forcedFullRefreshDueToGap) {
|
|
81
87
|
finalSnapshot = cachedEntry.snapshot;
|
|
82
88
|
}
|
|
83
89
|
else {
|
|
@@ -151,7 +157,7 @@ export class DeltaCache {
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
async fetchWithoutDelta(cacheKey, _budgetId, fetcher, options) {
|
|
154
|
-
const effectiveTtl = this.assertFiniteTtl(
|
|
160
|
+
const effectiveTtl = this.assertFiniteTtl("fetchWithoutDelta", cacheKey, options.ttl);
|
|
155
161
|
const cachedEntry = this.cacheManager.get(cacheKey);
|
|
156
162
|
if (cachedEntry) {
|
|
157
163
|
const age = Date.now() - cachedEntry.timestamp;
|
|
@@ -201,6 +207,6 @@ export class DeltaCache {
|
|
|
201
207
|
return ttl;
|
|
202
208
|
}
|
|
203
209
|
isDeltaEnabled() {
|
|
204
|
-
return process.env[
|
|
210
|
+
return process.env["YNAB_MCP_ENABLE_DELTA"] === "true";
|
|
205
211
|
}
|
|
206
212
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import
|
|
1
|
+
import type * as ynab from "ynab";
|
|
2
|
+
import type { MergeFn, MergeOptions } from "./deltaCache.js";
|
|
3
3
|
export declare function mergeFlatEntities<T extends {
|
|
4
4
|
id: string;
|
|
5
5
|
deleted?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { CacheManager } from
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import type { CacheManager } from "./cacheManager.js";
|
|
2
|
+
import type { DeltaCache } from "./deltaCache.js";
|
|
3
|
+
import type { ServerKnowledgeStore } from "./serverKnowledgeStore.js";
|
|
4
4
|
export interface SecurityStatsProvider {
|
|
5
5
|
getSecurityStats(): unknown;
|
|
6
6
|
}
|
|
@@ -82,7 +82,7 @@ export declare class DiagnosticManager {
|
|
|
82
82
|
constructor(dependencies: DiagnosticDependencies);
|
|
83
83
|
collectDiagnostics(options: DiagnosticOptions): Promise<{
|
|
84
84
|
content: {
|
|
85
|
-
type:
|
|
85
|
+
type: "text";
|
|
86
86
|
text: string;
|
|
87
87
|
}[];
|
|
88
88
|
}>;
|