@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,9 +1,9 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
function parseBool(value, fallback) {
|
|
3
3
|
if (value === undefined)
|
|
4
4
|
return fallback;
|
|
5
5
|
const v = value.trim().toLowerCase();
|
|
6
|
-
return v ===
|
|
6
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
7
7
|
}
|
|
8
8
|
function parseIntSafe(value, fallback) {
|
|
9
9
|
if (value === undefined)
|
|
@@ -14,15 +14,15 @@ function parseIntSafe(value, fallback) {
|
|
|
14
14
|
class ResponseFormatter {
|
|
15
15
|
constructor() {
|
|
16
16
|
this.als = new AsyncLocalStorage();
|
|
17
|
-
this.defaultMinify = parseBool(process.env[
|
|
18
|
-
this.prettySpaces = parseIntSafe(process.env[
|
|
17
|
+
this.defaultMinify = parseBool(process.env["YNAB_MCP_MINIFY_OUTPUT"], true);
|
|
18
|
+
this.prettySpaces = parseIntSafe(process.env["YNAB_MCP_PRETTY_SPACES"], 2);
|
|
19
19
|
}
|
|
20
20
|
configure(options) {
|
|
21
21
|
if (!options)
|
|
22
22
|
return;
|
|
23
|
-
if (typeof options.defaultMinify ===
|
|
23
|
+
if (typeof options.defaultMinify === "boolean")
|
|
24
24
|
this.defaultMinify = options.defaultMinify;
|
|
25
|
-
if (typeof options.prettySpaces ===
|
|
25
|
+
if (typeof options.prettySpaces === "number" && options.prettySpaces >= 0) {
|
|
26
26
|
this.prettySpaces = options.prettySpaces;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CallToolResult } from
|
|
2
|
-
import { z } from
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
3
|
export interface SecurityContext {
|
|
4
4
|
accessToken: string;
|
|
5
5
|
toolName: string;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { responseFormatter } from
|
|
1
|
+
import { fromZodError } from "zod-validation-error";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import { createErrorHandler } from "./errorHandler.js";
|
|
4
|
+
import { RateLimitError, globalRateLimiter } from "./rateLimiter.js";
|
|
5
|
+
import { globalRequestLogger } from "./requestLogger.js";
|
|
6
|
+
import { responseFormatter } from "./responseFormatter.js";
|
|
7
7
|
export class SecurityMiddleware {
|
|
8
8
|
static async withSecurity(context, schema, operation) {
|
|
9
9
|
const startTime = Date.now();
|
|
10
10
|
try {
|
|
11
|
-
const validatedParams = await
|
|
12
|
-
await
|
|
13
|
-
globalRateLimiter.recordRequest(
|
|
11
|
+
const validatedParams = await SecurityMiddleware.validateInput(schema, context.parameters);
|
|
12
|
+
await SecurityMiddleware.checkRateLimit(context.accessToken);
|
|
13
|
+
globalRateLimiter.recordRequest(SecurityMiddleware.hashToken(context.accessToken));
|
|
14
14
|
const result = await operation(validatedParams);
|
|
15
15
|
const duration = Date.now() - startTime;
|
|
16
|
-
const rateLimitInfo = globalRateLimiter.getStatus(
|
|
16
|
+
const rateLimitInfo = globalRateLimiter.getStatus(SecurityMiddleware.hashToken(context.accessToken));
|
|
17
17
|
globalRequestLogger.logSuccess(context.toolName, context.operation, context.parameters, duration, {
|
|
18
18
|
remaining: rateLimitInfo.remaining,
|
|
19
19
|
isLimited: rateLimitInfo.isLimited,
|
|
@@ -22,17 +22,19 @@ export class SecurityMiddleware {
|
|
|
22
22
|
}
|
|
23
23
|
catch (error) {
|
|
24
24
|
const duration = Date.now() - startTime;
|
|
25
|
-
const rateLimitInfo = globalRateLimiter.getStatus(
|
|
26
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
25
|
+
const rateLimitInfo = globalRateLimiter.getStatus(SecurityMiddleware.hashToken(context.accessToken));
|
|
26
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
27
27
|
globalRequestLogger.logError(context.toolName, context.operation, context.parameters, errorMessage, duration, {
|
|
28
28
|
remaining: rateLimitInfo.remaining,
|
|
29
29
|
isLimited: rateLimitInfo.isLimited,
|
|
30
30
|
});
|
|
31
31
|
if (error instanceof RateLimitError) {
|
|
32
|
-
return
|
|
32
|
+
return SecurityMiddleware.createRateLimitErrorResponse(error);
|
|
33
33
|
}
|
|
34
|
-
if (error instanceof Error &&
|
|
35
|
-
|
|
34
|
+
if (error instanceof Error &&
|
|
35
|
+
error.message.includes("Validation failed")) {
|
|
36
|
+
const errorHandler = createErrorHandler(responseFormatter);
|
|
37
|
+
return errorHandler.createValidationError(`Invalid parameters for ${context.toolName}`, error.message);
|
|
36
38
|
}
|
|
37
39
|
throw error;
|
|
38
40
|
}
|
|
@@ -50,10 +52,10 @@ export class SecurityMiddleware {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
static async checkRateLimit(accessToken) {
|
|
53
|
-
const tokenHash =
|
|
55
|
+
const tokenHash = SecurityMiddleware.hashToken(accessToken);
|
|
54
56
|
const rateLimitInfo = globalRateLimiter.isAllowed(tokenHash);
|
|
55
57
|
if (rateLimitInfo.isLimited) {
|
|
56
|
-
throw new RateLimitError(
|
|
58
|
+
throw new RateLimitError("Rate limit exceeded. Please wait before making additional requests.", rateLimitInfo.resetTime, rateLimitInfo.remaining);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
static createRateLimitErrorResponse(error) {
|
|
@@ -61,10 +63,10 @@ export class SecurityMiddleware {
|
|
|
61
63
|
isError: true,
|
|
62
64
|
content: [
|
|
63
65
|
{
|
|
64
|
-
type:
|
|
66
|
+
type: "text",
|
|
65
67
|
text: responseFormatter.format({
|
|
66
68
|
error: {
|
|
67
|
-
code:
|
|
69
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
68
70
|
message: error.message,
|
|
69
71
|
details: {
|
|
70
72
|
resetTime: error.resetTime.toISOString(),
|
|
@@ -88,7 +90,7 @@ export class SecurityMiddleware {
|
|
|
88
90
|
static getSecurityStats() {
|
|
89
91
|
return {
|
|
90
92
|
rateLimitStats: {
|
|
91
|
-
message:
|
|
93
|
+
message: "Rate limiting is active with YNAB API limits (200 requests/hour)",
|
|
92
94
|
},
|
|
93
95
|
requestStats: globalRequestLogger.getStats(),
|
|
94
96
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { CallToolResult, Tool } from
|
|
2
|
-
import { z } from
|
|
3
|
-
import type { MCPToolAnnotations } from
|
|
1
|
+
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import type { MCPToolAnnotations } from "../types/toolAnnotations.js";
|
|
4
4
|
export type SecurityWrapperFactory = <T extends Record<string, unknown>>(namespace: string, operation: string, schema: z.ZodSchema<T>) => (accessToken: string) => (params: Record<string, unknown>) => (handler: (validated: T) => Promise<CallToolResult>) => Promise<CallToolResult>;
|
|
5
5
|
export interface ErrorHandlerContract {
|
|
6
6
|
handleError(error: unknown, context: string): CallToolResult;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { fromZodError } from "zod-validation-error";
|
|
2
|
+
import { toJSONSchema, z } from "zod/v4";
|
|
3
3
|
export class DefaultArgumentResolutionError extends Error {
|
|
4
4
|
constructor(result) {
|
|
5
|
-
super(
|
|
5
|
+
super("Default argument resolution failed");
|
|
6
6
|
this.result = result;
|
|
7
|
-
this.name =
|
|
7
|
+
this.name = "DefaultArgumentResolutionError";
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
const MINIFY_HINT_KEYS = [
|
|
10
|
+
const MINIFY_HINT_KEYS = ["minify", "_minify", "__minify"];
|
|
11
11
|
export class ToolRegistry {
|
|
12
12
|
constructor(deps) {
|
|
13
13
|
this.deps = deps;
|
|
@@ -22,7 +22,7 @@ export class ToolRegistry {
|
|
|
22
22
|
const resolved = {
|
|
23
23
|
...definition,
|
|
24
24
|
security: {
|
|
25
|
-
namespace: definition.security?.namespace ??
|
|
25
|
+
namespace: definition.security?.namespace ?? "ynab",
|
|
26
26
|
operation: definition.security?.operation ?? definition.name,
|
|
27
27
|
},
|
|
28
28
|
};
|
|
@@ -42,7 +42,7 @@ export class ToolRegistry {
|
|
|
42
42
|
inputSchema,
|
|
43
43
|
};
|
|
44
44
|
if (tool.outputSchema) {
|
|
45
|
-
const outputSchema = this.generateJsonSchema(tool.outputSchema,
|
|
45
|
+
const outputSchema = this.generateJsonSchema(tool.outputSchema, "output");
|
|
46
46
|
result.outputSchema = outputSchema;
|
|
47
47
|
}
|
|
48
48
|
if (tool.metadata?.annotations) {
|
|
@@ -78,7 +78,7 @@ export class ToolRegistry {
|
|
|
78
78
|
async executeTool(options) {
|
|
79
79
|
const tool = this.tools.get(options.name);
|
|
80
80
|
if (!tool) {
|
|
81
|
-
return this.deps.errorHandler.createValidationError(`Unknown tool: ${options.name}`,
|
|
81
|
+
return this.deps.errorHandler.createValidationError(`Unknown tool: ${options.name}`, "The requested tool is not registered with the server");
|
|
82
82
|
}
|
|
83
83
|
if (this.deps.validateAccessToken) {
|
|
84
84
|
try {
|
|
@@ -107,9 +107,9 @@ export class ToolRegistry {
|
|
|
107
107
|
if (this.isCallToolResult(error)) {
|
|
108
108
|
return error;
|
|
109
109
|
}
|
|
110
|
-
return this.deps.errorHandler.createValidationError(
|
|
110
|
+
return this.deps.errorHandler.createValidationError("Invalid parameters", error instanceof Error
|
|
111
111
|
? error.message
|
|
112
|
-
:
|
|
112
|
+
: "Unknown error during default argument resolution");
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
const rawArguments = {
|
|
@@ -160,9 +160,9 @@ export class ToolRegistry {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
isCallToolResult(value) {
|
|
163
|
-
return (typeof value ===
|
|
163
|
+
return (typeof value === "object" &&
|
|
164
164
|
value !== null &&
|
|
165
|
-
|
|
165
|
+
"content" in value &&
|
|
166
166
|
Array.isArray(value.content));
|
|
167
167
|
}
|
|
168
168
|
normalizeSecurityError(error, tool) {
|
|
@@ -170,18 +170,18 @@ export class ToolRegistry {
|
|
|
170
170
|
const validationError = fromZodError(error);
|
|
171
171
|
return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, validationError.message);
|
|
172
172
|
}
|
|
173
|
-
if (error instanceof Error && error.message.includes(
|
|
173
|
+
if (error instanceof Error && error.message.includes("Validation failed")) {
|
|
174
174
|
return this.deps.errorHandler.createValidationError(`Invalid parameters for ${tool.name}`, error.message);
|
|
175
175
|
}
|
|
176
176
|
return this.deps.errorHandler.handleError(error, `executing ${tool.name}`);
|
|
177
177
|
}
|
|
178
178
|
extractMinifyOverride(options, args) {
|
|
179
|
-
if (typeof options.minifyOverride ===
|
|
179
|
+
if (typeof options.minifyOverride === "boolean") {
|
|
180
180
|
return options.minifyOverride;
|
|
181
181
|
}
|
|
182
182
|
for (const key of MINIFY_HINT_KEYS) {
|
|
183
183
|
const value = args[key];
|
|
184
|
-
if (typeof value ===
|
|
184
|
+
if (typeof value === "boolean") {
|
|
185
185
|
delete args[key];
|
|
186
186
|
return value;
|
|
187
187
|
}
|
|
@@ -189,40 +189,41 @@ export class ToolRegistry {
|
|
|
189
189
|
return undefined;
|
|
190
190
|
}
|
|
191
191
|
assertValidDefinition(definition) {
|
|
192
|
-
if (!definition || typeof definition !==
|
|
193
|
-
throw new Error(
|
|
192
|
+
if (!definition || typeof definition !== "object") {
|
|
193
|
+
throw new Error("Tool definition must be an object");
|
|
194
194
|
}
|
|
195
|
-
if (!definition.name || typeof definition.name !==
|
|
196
|
-
throw new Error(
|
|
195
|
+
if (!definition.name || typeof definition.name !== "string") {
|
|
196
|
+
throw new Error("Tool definition requires a non-empty name");
|
|
197
197
|
}
|
|
198
198
|
if (!ToolRegistry.MCP_TOOL_NAME_REGEX.test(definition.name)) {
|
|
199
|
-
throw new Error(`Tool name '${definition.name}' violates MCP guidelines: `
|
|
200
|
-
`must be 1-128 chars using only [a-zA-Z0-9_.-]`);
|
|
199
|
+
throw new Error(`Tool name '${definition.name}' violates MCP guidelines: must be 1-128 chars using only [a-zA-Z0-9_.-]`);
|
|
201
200
|
}
|
|
202
|
-
if (!definition.description || typeof definition.description !==
|
|
201
|
+
if (!definition.description || typeof definition.description !== "string") {
|
|
203
202
|
throw new Error(`Tool '${definition.name}' requires a description`);
|
|
204
203
|
}
|
|
205
|
-
if (!definition.inputSchema ||
|
|
204
|
+
if (!definition.inputSchema ||
|
|
205
|
+
typeof definition.inputSchema.parse !== "function") {
|
|
206
206
|
throw new Error(`Tool '${definition.name}' requires a valid Zod schema`);
|
|
207
207
|
}
|
|
208
|
-
if (definition.outputSchema &&
|
|
208
|
+
if (definition.outputSchema &&
|
|
209
|
+
typeof definition.outputSchema.parse !== "function") {
|
|
209
210
|
throw new Error(`Tool '${definition.name}' outputSchema must be a valid Zod schema when provided`);
|
|
210
211
|
}
|
|
211
|
-
if (typeof definition.handler !==
|
|
212
|
+
if (typeof definition.handler !== "function") {
|
|
212
213
|
throw new Error(`Tool '${definition.name}' requires a handler function`);
|
|
213
214
|
}
|
|
214
215
|
if (definition.defaultArgumentResolver &&
|
|
215
|
-
typeof definition.defaultArgumentResolver !==
|
|
216
|
+
typeof definition.defaultArgumentResolver !== "function") {
|
|
216
217
|
throw new Error(`Tool '${definition.name}' defaultArgumentResolver must be a function when provided`);
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
|
-
generateJsonSchema(schema, ioMode =
|
|
220
|
+
generateJsonSchema(schema, ioMode = "input") {
|
|
220
221
|
try {
|
|
221
|
-
return toJSONSchema(schema, { target:
|
|
222
|
+
return toJSONSchema(schema, { target: "draft-2020-12", io: ioMode });
|
|
222
223
|
}
|
|
223
224
|
catch (error) {
|
|
224
225
|
console.warn(`Failed to generate JSON schema for tool: ${error}`);
|
|
225
|
-
return { type:
|
|
226
|
+
return { type: "object", additionalProperties: true };
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
validateOutput(toolName, output) {
|
|
@@ -231,18 +232,21 @@ export class ToolRegistry {
|
|
|
231
232
|
return output;
|
|
232
233
|
}
|
|
233
234
|
if (!output.content || output.content.length === 0) {
|
|
234
|
-
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`,
|
|
235
|
+
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, "Handler returned empty content", ["Ensure the handler returns valid content in the response"]);
|
|
235
236
|
}
|
|
236
237
|
const invalidItems = [];
|
|
237
238
|
for (let i = 0; i < output.content.length; i++) {
|
|
238
239
|
const item = output.content[i];
|
|
239
240
|
if (!item) {
|
|
240
|
-
invalidItems.push({ index: i, reason:
|
|
241
|
+
invalidItems.push({ index: i, reason: "item is null or undefined" });
|
|
241
242
|
}
|
|
242
|
-
else if (item.type !==
|
|
243
|
-
invalidItems.push({
|
|
243
|
+
else if (item.type !== "text") {
|
|
244
|
+
invalidItems.push({
|
|
245
|
+
index: i,
|
|
246
|
+
reason: `type is "${item.type}" instead of "text"`,
|
|
247
|
+
});
|
|
244
248
|
}
|
|
245
|
-
else if (typeof item.text !==
|
|
249
|
+
else if (typeof item.text !== "string") {
|
|
246
250
|
invalidItems.push({
|
|
247
251
|
index: i,
|
|
248
252
|
reason: `text property is ${typeof item.text} instead of string`,
|
|
@@ -252,27 +256,30 @@ export class ToolRegistry {
|
|
|
252
256
|
if (invalidItems.length > 0) {
|
|
253
257
|
const invalidItemsDetails = invalidItems
|
|
254
258
|
.map((inv) => ` - Item ${inv.index}: ${inv.reason}`)
|
|
255
|
-
.join(
|
|
259
|
+
.join("\n");
|
|
256
260
|
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Handler returned invalid content items (${invalidItems.length} of ${output.content.length} failed):\n${invalidItemsDetails}`, ['Ensure all content items have type="text" and a valid text property']);
|
|
257
261
|
}
|
|
258
262
|
const firstContent = output.content[0];
|
|
259
|
-
if (firstContent
|
|
260
|
-
|
|
263
|
+
if (!firstContent) {
|
|
264
|
+
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, "Handler returned empty content", ["Ensure the handler returns valid content in the response"]);
|
|
265
|
+
}
|
|
266
|
+
if (firstContent.type !== "text") {
|
|
267
|
+
throw new Error("Unexpected: firstContent is not text after validation");
|
|
261
268
|
}
|
|
262
269
|
let parsedOutput;
|
|
263
270
|
try {
|
|
264
271
|
parsedOutput = JSON.parse(firstContent.text);
|
|
265
272
|
}
|
|
266
273
|
catch (parseError) {
|
|
267
|
-
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Invalid JSON in handler output: ${parseError instanceof Error ? parseError.message : String(parseError)}`, [
|
|
274
|
+
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Invalid JSON in handler output: ${parseError instanceof Error ? parseError.message : String(parseError)}`, ["Ensure the handler returns valid JSON"]);
|
|
268
275
|
}
|
|
269
276
|
const result = validator.safeParse(parsedOutput);
|
|
270
277
|
if (!result.success) {
|
|
271
278
|
const validationError = fromZodError(result.error);
|
|
272
279
|
const validationErrors = validationError.message;
|
|
273
280
|
return this.deps.errorHandler.createValidationError(`Output validation failed for ${toolName}`, `Handler output does not match declared output schema: ${validationErrors}`, [
|
|
274
|
-
|
|
275
|
-
|
|
281
|
+
"Check that the handler returns data matching the output schema",
|
|
282
|
+
"Review the tool definition output schema",
|
|
276
283
|
]);
|
|
277
284
|
}
|
|
278
285
|
return output;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { vi } from
|
|
2
|
-
import { DeltaFetcher } from
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { DeltaFetcher } from "../deltaFetcher.js";
|
|
3
3
|
export interface MockDeltaResult<T> {
|
|
4
4
|
data: T[];
|
|
5
5
|
wasCached?: boolean;
|
|
6
6
|
usedDelta?: boolean;
|
|
7
7
|
}
|
|
8
|
-
type DeltaFetcherMethod =
|
|
8
|
+
type DeltaFetcherMethod = "fetchAccounts" | "fetchBudgets" | "fetchCategories" | "fetchMonths";
|
|
9
9
|
export declare function createDeltaFetcherMock<T>(method: DeltaFetcherMethod, result: MockDeltaResult<T>): {
|
|
10
10
|
fetcher: DeltaFetcher;
|
|
11
11
|
spy: ReturnType<typeof vi.fn>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { CallToolResult } from
|
|
2
|
-
import * as ynab from
|
|
3
|
-
import { z } from
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type { ServerKnowledgeStore } from
|
|
7
|
-
import type { ToolFactory } from
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type * as ynab from "ynab";
|
|
3
|
+
import { z } from "zod/v4";
|
|
4
|
+
import type { DeltaCache } from "../server/deltaCache.js";
|
|
5
|
+
import type { ErrorHandler } from "../server/errorHandler.js";
|
|
6
|
+
import type { ServerKnowledgeStore } from "../server/serverKnowledgeStore.js";
|
|
7
|
+
import type { ToolFactory } from "../types/toolRegistration.js";
|
|
8
|
+
import type { DeltaFetcher } from "./deltaFetcher.js";
|
|
8
9
|
export declare const ListAccountsSchema: z.ZodObject<{
|
|
9
10
|
budget_id: z.ZodString;
|
|
10
11
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
@@ -33,7 +34,7 @@ export declare const CreateAccountSchema: z.ZodObject<{
|
|
|
33
34
|
export type CreateAccountParams = z.infer<typeof CreateAccountSchema>;
|
|
34
35
|
export declare function handleListAccounts(ynabAPI: ynab.API, deltaFetcher: DeltaFetcher, params: ListAccountsParams): Promise<CallToolResult>;
|
|
35
36
|
export declare function handleListAccounts(ynabAPI: ynab.API, params: ListAccountsParams): Promise<CallToolResult>;
|
|
36
|
-
export declare function handleGetAccount(ynabAPI: ynab.API, params: GetAccountParams): Promise<CallToolResult>;
|
|
37
|
+
export declare function handleGetAccount(ynabAPI: ynab.API, params: GetAccountParams, errorHandler?: ErrorHandler): Promise<CallToolResult>;
|
|
37
38
|
export declare function handleCreateAccount(ynabAPI: ynab.API, deltaCache: DeltaCache, knowledgeStore: ServerKnowledgeStore, params: CreateAccountParams): Promise<CallToolResult>;
|
|
38
39
|
export declare function handleCreateAccount(ynabAPI: ynab.API, params: CreateAccountParams): Promise<CallToolResult>;
|
|
39
40
|
export declare const registerAccountTools: ToolFactory;
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { ToolAnnotationPresets } from
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { CacheKeys } from "../server/cacheKeys.js";
|
|
3
|
+
import { CACHE_TTLS, CacheManager, cacheManager, } from "../server/cacheManager.js";
|
|
4
|
+
import { responseFormatter } from "../server/responseFormatter.js";
|
|
5
|
+
import { withToolErrorHandling } from "../types/index.js";
|
|
6
|
+
import { milliunitsToAmount } from "../utils/amountUtils.js";
|
|
7
|
+
import { createAdapters, createBudgetResolver } from "./adapters.js";
|
|
8
|
+
import { resolveDeltaFetcherArgs, resolveDeltaWriteArgs, } from "./deltaSupport.js";
|
|
9
|
+
import { ToolAnnotationPresets } from "./toolCategories.js";
|
|
10
10
|
export const ListAccountsSchema = z
|
|
11
11
|
.object({
|
|
12
|
-
budget_id: z.string().min(1,
|
|
12
|
+
budget_id: z.string().min(1, "Budget ID is required"),
|
|
13
13
|
limit: z.number().int().positive().optional(),
|
|
14
14
|
})
|
|
15
15
|
.strict();
|
|
16
16
|
export const GetAccountSchema = z
|
|
17
17
|
.object({
|
|
18
|
-
budget_id: z.string().min(1,
|
|
19
|
-
account_id: z.string().min(1,
|
|
18
|
+
budget_id: z.string().min(1, "Budget ID is required"),
|
|
19
|
+
account_id: z.string().min(1, "Account ID is required"),
|
|
20
20
|
})
|
|
21
21
|
.strict();
|
|
22
22
|
export const CreateAccountSchema = z
|
|
23
23
|
.object({
|
|
24
|
-
budget_id: z.string().min(1,
|
|
25
|
-
name: z.string().min(1,
|
|
24
|
+
budget_id: z.string().min(1, "Budget ID is required"),
|
|
25
|
+
name: z.string().min(1, "Account name is required"),
|
|
26
26
|
type: z.enum([
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
"checking",
|
|
28
|
+
"savings",
|
|
29
|
+
"creditCard",
|
|
30
|
+
"cash",
|
|
31
|
+
"lineOfCredit",
|
|
32
|
+
"otherAsset",
|
|
33
|
+
"otherLiability",
|
|
34
34
|
]),
|
|
35
35
|
balance: z.number().optional(),
|
|
36
36
|
dry_run: z.boolean().optional(),
|
|
37
37
|
})
|
|
38
38
|
.strict();
|
|
39
|
-
export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybeParams) {
|
|
39
|
+
export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybeParams, errorHandler) {
|
|
40
40
|
const { deltaFetcher, params } = resolveDeltaFetcherArgs(ynabAPI, deltaFetcherOrParams, maybeParams);
|
|
41
41
|
return await withToolErrorHandling(async () => {
|
|
42
42
|
const result = await deltaFetcher.fetchAccounts(params.budget_id);
|
|
@@ -49,7 +49,7 @@ export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybePar
|
|
|
49
49
|
return {
|
|
50
50
|
content: [
|
|
51
51
|
{
|
|
52
|
-
type:
|
|
52
|
+
type: "text",
|
|
53
53
|
text: responseFormatter.format({
|
|
54
54
|
accounts: accounts.map((account) => ({
|
|
55
55
|
id: account.id,
|
|
@@ -69,17 +69,17 @@ export async function handleListAccounts(ynabAPI, deltaFetcherOrParams, maybePar
|
|
|
69
69
|
returned_count: accounts.length,
|
|
70
70
|
cached: wasCached,
|
|
71
71
|
cache_info: wasCached
|
|
72
|
-
? `Data retrieved from cache for improved performance${result.usedDelta ?
|
|
73
|
-
:
|
|
72
|
+
? `Data retrieved from cache for improved performance${result.usedDelta ? " (delta merge applied)" : ""}`
|
|
73
|
+
: "Fresh data retrieved from YNAB API",
|
|
74
74
|
}),
|
|
75
75
|
},
|
|
76
76
|
],
|
|
77
77
|
};
|
|
78
|
-
},
|
|
78
|
+
}, "ynab:list_accounts", "listing accounts", errorHandler);
|
|
79
79
|
}
|
|
80
|
-
export async function handleGetAccount(ynabAPI, params) {
|
|
80
|
+
export async function handleGetAccount(ynabAPI, params, errorHandler) {
|
|
81
81
|
return await withToolErrorHandling(async () => {
|
|
82
|
-
const cacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS,
|
|
82
|
+
const cacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, "get", params.budget_id, params.account_id);
|
|
83
83
|
const wasCached = cacheManager.has(cacheKey);
|
|
84
84
|
const account = await cacheManager.wrap(cacheKey, {
|
|
85
85
|
ttl: CACHE_TTLS.ACCOUNTS,
|
|
@@ -91,7 +91,7 @@ export async function handleGetAccount(ynabAPI, params) {
|
|
|
91
91
|
return {
|
|
92
92
|
content: [
|
|
93
93
|
{
|
|
94
|
-
type:
|
|
94
|
+
type: "text",
|
|
95
95
|
text: responseFormatter.format({
|
|
96
96
|
account: {
|
|
97
97
|
id: account.id,
|
|
@@ -109,25 +109,25 @@ export async function handleGetAccount(ynabAPI, params) {
|
|
|
109
109
|
},
|
|
110
110
|
cached: wasCached,
|
|
111
111
|
cache_info: wasCached
|
|
112
|
-
?
|
|
113
|
-
:
|
|
112
|
+
? "Data retrieved from cache for improved performance"
|
|
113
|
+
: "Fresh data retrieved from YNAB API",
|
|
114
114
|
}),
|
|
115
115
|
},
|
|
116
116
|
],
|
|
117
117
|
};
|
|
118
|
-
},
|
|
118
|
+
}, "ynab:get_account", "getting account details", errorHandler);
|
|
119
119
|
}
|
|
120
|
-
export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledgeStoreOrParams, maybeParams) {
|
|
120
|
+
export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledgeStoreOrParams, maybeParams, errorHandler) {
|
|
121
121
|
const { deltaCache, params } = resolveDeltaWriteArgs(deltaCacheOrParams, knowledgeStoreOrParams, maybeParams);
|
|
122
122
|
return await withToolErrorHandling(async () => {
|
|
123
123
|
if (params.dry_run) {
|
|
124
124
|
return {
|
|
125
125
|
content: [
|
|
126
126
|
{
|
|
127
|
-
type:
|
|
127
|
+
type: "text",
|
|
128
128
|
text: responseFormatter.format({
|
|
129
129
|
dry_run: true,
|
|
130
|
-
action:
|
|
130
|
+
action: "create_account",
|
|
131
131
|
request: {
|
|
132
132
|
budget_id: params.budget_id,
|
|
133
133
|
name: params.name,
|
|
@@ -148,13 +148,13 @@ export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledge
|
|
|
148
148
|
account: accountData,
|
|
149
149
|
});
|
|
150
150
|
const account = response.data.account;
|
|
151
|
-
const accountsListCacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS,
|
|
151
|
+
const accountsListCacheKey = CacheManager.generateKey(CacheKeys.ACCOUNTS, "list", params.budget_id);
|
|
152
152
|
cacheManager.delete(accountsListCacheKey);
|
|
153
153
|
deltaCache.invalidate(params.budget_id, CacheKeys.ACCOUNTS);
|
|
154
154
|
return {
|
|
155
155
|
content: [
|
|
156
156
|
{
|
|
157
|
-
type:
|
|
157
|
+
type: "text",
|
|
158
158
|
text: responseFormatter.format({
|
|
159
159
|
account: {
|
|
160
160
|
id: account.id,
|
|
@@ -174,47 +174,47 @@ export async function handleCreateAccount(ynabAPI, deltaCacheOrParams, knowledge
|
|
|
174
174
|
},
|
|
175
175
|
],
|
|
176
176
|
};
|
|
177
|
-
},
|
|
177
|
+
}, "ynab:create_account", "creating account", errorHandler);
|
|
178
178
|
}
|
|
179
179
|
export const registerAccountTools = (registry, context) => {
|
|
180
180
|
const { adapt, adaptWithDelta, adaptWrite } = createAdapters(context);
|
|
181
181
|
const budgetResolver = createBudgetResolver(context);
|
|
182
182
|
registry.register({
|
|
183
|
-
name:
|
|
184
|
-
description:
|
|
183
|
+
name: "list_accounts",
|
|
184
|
+
description: "List all accounts for a specific budget",
|
|
185
185
|
inputSchema: ListAccountsSchema,
|
|
186
186
|
handler: adaptWithDelta(handleListAccounts),
|
|
187
187
|
defaultArgumentResolver: budgetResolver(),
|
|
188
188
|
metadata: {
|
|
189
189
|
annotations: {
|
|
190
190
|
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
191
|
-
title:
|
|
191
|
+
title: "YNAB: List Accounts",
|
|
192
192
|
},
|
|
193
193
|
},
|
|
194
194
|
});
|
|
195
195
|
registry.register({
|
|
196
|
-
name:
|
|
197
|
-
description:
|
|
196
|
+
name: "get_account",
|
|
197
|
+
description: "Get detailed information for a specific account",
|
|
198
198
|
inputSchema: GetAccountSchema,
|
|
199
199
|
handler: adapt(handleGetAccount),
|
|
200
200
|
defaultArgumentResolver: budgetResolver(),
|
|
201
201
|
metadata: {
|
|
202
202
|
annotations: {
|
|
203
203
|
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
204
|
-
title:
|
|
204
|
+
title: "YNAB: Get Account Details",
|
|
205
205
|
},
|
|
206
206
|
},
|
|
207
207
|
});
|
|
208
208
|
registry.register({
|
|
209
|
-
name:
|
|
210
|
-
description:
|
|
209
|
+
name: "create_account",
|
|
210
|
+
description: "Create a new account in the specified budget",
|
|
211
211
|
inputSchema: CreateAccountSchema,
|
|
212
212
|
handler: adaptWrite(handleCreateAccount),
|
|
213
213
|
defaultArgumentResolver: budgetResolver(),
|
|
214
214
|
metadata: {
|
|
215
215
|
annotations: {
|
|
216
216
|
...ToolAnnotationPresets.WRITE_EXTERNAL_CREATE,
|
|
217
|
-
title:
|
|
217
|
+
title: "YNAB: Create Account",
|
|
218
218
|
},
|
|
219
219
|
},
|
|
220
220
|
});
|