@dizzlkheinz/ynab-mcpb 0.18.4 → 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/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 +19 -12
- package/dist/tools/reconciliation/analyzer.d.ts +4 -4
- package/dist/tools/reconciliation/analyzer.js +73 -59
- 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 +59 -58
- package/dist/tools/reconciliation/signDetector.d.ts +1 -1
- package/dist/tools/reconciliation/types.d.ts +16 -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 +7 -7
- package/dist/tools/transactionSchemas.js +77 -57
- package/dist/tools/transactionTools.d.ts +6 -24
- package/dist/tools/transactionTools.js +7 -1499
- package/dist/tools/transactionUtils.d.ts +6 -6
- package/dist/tools/transactionUtils.js +78 -63
- 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/esbuild.config.mjs +53 -50
- package/meta.json +12548 -12548
- package/package.json +98 -111
- package/scripts/analyze-bundle.mjs +33 -30
- package/scripts/create-pr-description.js +169 -120
- package/scripts/run-all-tests.js +178 -169
- 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 +1202 -1186
- 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 +1004 -977
- 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 -252
- package/src/tools/reconciliation/CLAUDE.md +506 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +133 -124
- package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -230
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -400
- 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 -989
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -533
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -74
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -62
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +56 -55
- 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 +564 -504
- 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 +343 -307
- package/src/tools/reconciliation/signDetector.ts +89 -83
- package/src/tools/reconciliation/types.ts +164 -159
- 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 +322 -291
- package/src/tools/transactionTools.ts +84 -2246
- package/src/tools/transactionUtils.ts +507 -422
- 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,4 +1,4 @@
|
|
|
1
|
-
import type { MCPToolAnnotations } from
|
|
1
|
+
import type { MCPToolAnnotations } from "../types/toolAnnotations.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Preset annotation patterns for common tool categories.
|
|
@@ -20,120 +20,120 @@ import type { MCPToolAnnotations } from '../types/toolAnnotations.js';
|
|
|
20
20
|
* });
|
|
21
21
|
*/
|
|
22
22
|
export const ToolAnnotationPresets = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Preset for read-only tools that query the YNAB API without modifications.
|
|
25
|
+
*
|
|
26
|
+
* Use this for tools that only retrieve data from YNAB without making any changes.
|
|
27
|
+
*
|
|
28
|
+
* Examples:
|
|
29
|
+
* - list_budgets: Retrieves all budgets
|
|
30
|
+
* - get_account: Retrieves account details
|
|
31
|
+
* - list_transactions: Retrieves transaction data
|
|
32
|
+
* - list_categories: Retrieves category information
|
|
33
|
+
* - get_month: Retrieves monthly budget data
|
|
34
|
+
*
|
|
35
|
+
* Annotation rationale:
|
|
36
|
+
* - readOnlyHint: true - Tool only reads data, never modifies
|
|
37
|
+
* - destructiveHint: false - No destructive operations performed
|
|
38
|
+
* - idempotentHint: true - Same query returns same data (barring external changes)
|
|
39
|
+
* - openWorldHint: true - Calls external YNAB API over network
|
|
40
|
+
*/
|
|
41
|
+
READ_ONLY_EXTERNAL: {
|
|
42
|
+
readOnlyHint: true,
|
|
43
|
+
destructiveHint: false,
|
|
44
|
+
idempotentHint: true,
|
|
45
|
+
openWorldHint: true,
|
|
46
|
+
},
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Preset for tools that create new resources in YNAB.
|
|
50
|
+
*
|
|
51
|
+
* Use this for tools that add new entities to YNAB without modifying existing ones.
|
|
52
|
+
*
|
|
53
|
+
* Examples:
|
|
54
|
+
* - create_transaction: Creates a new transaction
|
|
55
|
+
* - create_account: Creates a new account
|
|
56
|
+
* - create_category: Creates a new budget category
|
|
57
|
+
*
|
|
58
|
+
* Annotation rationale:
|
|
59
|
+
* - readOnlyHint: false - Tool modifies data by creating resources
|
|
60
|
+
* - destructiveHint: false - Creation is not destructive (doesn't delete/overwrite)
|
|
61
|
+
* - idempotentHint: false - Repeated calls create multiple distinct resources
|
|
62
|
+
* - openWorldHint: true - Calls external YNAB API over network
|
|
63
|
+
*/
|
|
64
|
+
WRITE_EXTERNAL_CREATE: {
|
|
65
|
+
readOnlyHint: false,
|
|
66
|
+
destructiveHint: false,
|
|
67
|
+
idempotentHint: false,
|
|
68
|
+
openWorldHint: true,
|
|
69
|
+
},
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Preset for tools that update existing resources in YNAB.
|
|
73
|
+
*
|
|
74
|
+
* Use this for tools that modify existing entities without deleting them.
|
|
75
|
+
*
|
|
76
|
+
* Examples:
|
|
77
|
+
* - update_transaction: Updates an existing transaction
|
|
78
|
+
* - set_default_budget: Sets the default budget preference
|
|
79
|
+
* - reconcile_account: Updates account balances and transaction states
|
|
80
|
+
*
|
|
81
|
+
* Annotation rationale:
|
|
82
|
+
* - readOnlyHint: false - Tool modifies existing data
|
|
83
|
+
* - destructiveHint: false - Updates are not destructive (reversible/editable)
|
|
84
|
+
* - idempotentHint: true - Repeated identical updates have same final state
|
|
85
|
+
* - openWorldHint: true - Calls external YNAB API over network
|
|
86
|
+
*/
|
|
87
|
+
WRITE_EXTERNAL_UPDATE: {
|
|
88
|
+
readOnlyHint: false,
|
|
89
|
+
destructiveHint: false,
|
|
90
|
+
idempotentHint: true,
|
|
91
|
+
openWorldHint: true,
|
|
92
|
+
},
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Preset for tools that delete resources from YNAB.
|
|
96
|
+
*
|
|
97
|
+
* Use this for tools that permanently remove entities from YNAB.
|
|
98
|
+
*
|
|
99
|
+
* Examples:
|
|
100
|
+
* - delete_transaction: Permanently deletes a transaction
|
|
101
|
+
* - delete_account: Removes an account
|
|
102
|
+
*
|
|
103
|
+
* Annotation rationale:
|
|
104
|
+
* - readOnlyHint: false - Tool modifies data by deleting
|
|
105
|
+
* - destructiveHint: true - Deletion is irreversible and destructive
|
|
106
|
+
* - idempotentHint: true - Deleting same resource multiple times has same effect
|
|
107
|
+
* - openWorldHint: true - Calls external YNAB API over network
|
|
108
|
+
*/
|
|
109
|
+
WRITE_EXTERNAL_DELETE: {
|
|
110
|
+
readOnlyHint: false,
|
|
111
|
+
destructiveHint: true,
|
|
112
|
+
idempotentHint: true,
|
|
113
|
+
openWorldHint: true,
|
|
114
|
+
},
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} as const satisfies Record<string, Omit<MCPToolAnnotations,
|
|
116
|
+
/**
|
|
117
|
+
* Preset for local utility tools that don't call external APIs.
|
|
118
|
+
*
|
|
119
|
+
* Use this for tools that perform local operations or calculations without
|
|
120
|
+
* interacting with the YNAB API.
|
|
121
|
+
*
|
|
122
|
+
* Examples:
|
|
123
|
+
* - set_output_format: Configures local output formatting
|
|
124
|
+
* - diagnostic_info: Returns local server diagnostic information
|
|
125
|
+
* - clear_cache: Clears local in-memory cache
|
|
126
|
+
*
|
|
127
|
+
* Annotation rationale:
|
|
128
|
+
* - readOnlyHint: true - No external/YNAB data modifications (may modify local server state/config)
|
|
129
|
+
* - destructiveHint: false - No destructive operations on external data
|
|
130
|
+
* - idempotentHint: true - Deterministic operations with same inputs (or safe to repeat)
|
|
131
|
+
* - openWorldHint: false - No external API calls, purely local operations
|
|
132
|
+
*/
|
|
133
|
+
UTILITY_LOCAL: {
|
|
134
|
+
readOnlyHint: true,
|
|
135
|
+
destructiveHint: false,
|
|
136
|
+
idempotentHint: true,
|
|
137
|
+
openWorldHint: false,
|
|
138
|
+
},
|
|
139
|
+
} as const satisfies Record<string, Omit<MCPToolAnnotations, "title">>;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type * as ynab from "ynab";
|
|
3
|
+
import type { z } from "zod/v4";
|
|
4
|
+
import {
|
|
5
|
+
CACHE_TTLS,
|
|
6
|
+
CacheManager,
|
|
7
|
+
cacheManager,
|
|
8
|
+
} from "../server/cacheManager.js";
|
|
9
|
+
import type { ErrorHandler } from "../server/errorHandler.js";
|
|
10
|
+
import { responseFormatter } from "../server/responseFormatter.js";
|
|
11
|
+
import type { ToolRegistry } from "../server/toolRegistry.js";
|
|
12
|
+
import { withToolErrorHandling } from "../types/index.js";
|
|
13
|
+
import type { ToolContext } from "../types/toolRegistration.js";
|
|
14
|
+
import { milliunitsToAmount } from "../utils/amountUtils.js";
|
|
15
|
+
import { createAdapters, createBudgetResolver } from "./adapters.js";
|
|
16
|
+
import type { DeltaFetcher } from "./deltaFetcher.js";
|
|
17
|
+
import { resolveDeltaFetcherArgs } from "./deltaSupport.js";
|
|
18
|
+
import {
|
|
19
|
+
ExportTransactionsSchema,
|
|
20
|
+
handleExportTransactions,
|
|
21
|
+
} from "./exportTransactions.js";
|
|
22
|
+
import { ToolAnnotationPresets } from "./toolCategories.js";
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
GetTransactionParams,
|
|
26
|
+
ListTransactionsParams,
|
|
27
|
+
} from "./transactionSchemas.js";
|
|
28
|
+
import {
|
|
29
|
+
GetTransactionSchema,
|
|
30
|
+
ListTransactionsSchema,
|
|
31
|
+
} from "./transactionSchemas.js";
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
ensureTransaction,
|
|
35
|
+
handleTransactionError,
|
|
36
|
+
} from "./transactionUtils.js";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handles the ynab:list_transactions tool call
|
|
40
|
+
* Lists transactions for a budget with optional filtering
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
export async function handleListTransactions(
|
|
44
|
+
ynabAPI: ynab.API,
|
|
45
|
+
deltaFetcher: DeltaFetcher,
|
|
46
|
+
params: ListTransactionsParams,
|
|
47
|
+
): Promise<CallToolResult>;
|
|
48
|
+
export async function handleListTransactions(
|
|
49
|
+
ynabAPI: ynab.API,
|
|
50
|
+
params: ListTransactionsParams,
|
|
51
|
+
): Promise<CallToolResult>;
|
|
52
|
+
export async function handleListTransactions(
|
|
53
|
+
ynabAPI: ynab.API,
|
|
54
|
+
deltaFetcherOrParams: DeltaFetcher | ListTransactionsParams,
|
|
55
|
+
maybeParams?: ListTransactionsParams,
|
|
56
|
+
errorHandler?: ErrorHandler,
|
|
57
|
+
): Promise<CallToolResult> {
|
|
58
|
+
const { deltaFetcher, params } = resolveDeltaFetcherArgs(
|
|
59
|
+
ynabAPI,
|
|
60
|
+
deltaFetcherOrParams,
|
|
61
|
+
maybeParams,
|
|
62
|
+
);
|
|
63
|
+
return await withToolErrorHandling(
|
|
64
|
+
async () => {
|
|
65
|
+
// Always use cache
|
|
66
|
+
let transactions: (ynab.TransactionDetail | ynab.HybridTransaction)[];
|
|
67
|
+
let cacheHit = false;
|
|
68
|
+
let usedDelta = false;
|
|
69
|
+
|
|
70
|
+
if (params.account_id) {
|
|
71
|
+
// Validate that the account exists before fetching transactions
|
|
72
|
+
// YNAB API returns empty array for invalid account IDs instead of an error
|
|
73
|
+
const accountsResult = await deltaFetcher.fetchAccounts(
|
|
74
|
+
params.budget_id,
|
|
75
|
+
);
|
|
76
|
+
const accountExists = accountsResult.data.some(
|
|
77
|
+
(account) => account.id === params.account_id,
|
|
78
|
+
);
|
|
79
|
+
if (!accountExists) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Account ${params.account_id} not found in budget ${params.budget_id}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await deltaFetcher.fetchTransactionsByAccount(
|
|
86
|
+
params.budget_id,
|
|
87
|
+
params.account_id,
|
|
88
|
+
params.since_date,
|
|
89
|
+
);
|
|
90
|
+
transactions = result.data;
|
|
91
|
+
cacheHit = result.wasCached;
|
|
92
|
+
usedDelta = result.usedDelta;
|
|
93
|
+
} else if (params.category_id) {
|
|
94
|
+
const response = await ynabAPI.transactions.getTransactionsByCategory(
|
|
95
|
+
params.budget_id,
|
|
96
|
+
params.category_id,
|
|
97
|
+
params.since_date,
|
|
98
|
+
);
|
|
99
|
+
transactions = response.data.transactions;
|
|
100
|
+
} else {
|
|
101
|
+
const result = await deltaFetcher.fetchTransactions(
|
|
102
|
+
params.budget_id,
|
|
103
|
+
params.since_date,
|
|
104
|
+
params.type as ynab.GetTransactionsTypeEnum | undefined,
|
|
105
|
+
);
|
|
106
|
+
transactions = result.data;
|
|
107
|
+
cacheHit = result.wasCached;
|
|
108
|
+
usedDelta = result.usedDelta;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if response might be too large for MCP
|
|
112
|
+
const estimatedSize = JSON.stringify(transactions).length;
|
|
113
|
+
const sizeLimit = 90000; // Conservative limit under 100KB
|
|
114
|
+
|
|
115
|
+
if (estimatedSize > sizeLimit) {
|
|
116
|
+
// Return summary and suggest export
|
|
117
|
+
const preview = transactions.slice(0, 50);
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: responseFormatter.format({
|
|
123
|
+
message: `Found ${transactions.length} transactions (${Math.round(estimatedSize / 1024)}KB). Too large to display all.`,
|
|
124
|
+
suggestion:
|
|
125
|
+
"Use 'export_transactions' tool to save all transactions to a file.",
|
|
126
|
+
showing: `First ${preview.length} transactions:`,
|
|
127
|
+
total_count: transactions.length,
|
|
128
|
+
estimated_size_kb: Math.round(estimatedSize / 1024),
|
|
129
|
+
cached: cacheHit,
|
|
130
|
+
cache_info: cacheHit
|
|
131
|
+
? `Data retrieved from cache for improved performance${usedDelta ? " (delta merge applied)" : ""}`
|
|
132
|
+
: "Fresh data retrieved from YNAB API",
|
|
133
|
+
preview_transactions: preview.map((transaction) => ({
|
|
134
|
+
id: transaction.id,
|
|
135
|
+
date: transaction.date,
|
|
136
|
+
amount: milliunitsToAmount(transaction.amount),
|
|
137
|
+
memo: transaction.memo,
|
|
138
|
+
payee_name: transaction.payee_name,
|
|
139
|
+
category_name: transaction.category_name,
|
|
140
|
+
})),
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: responseFormatter.format({
|
|
152
|
+
total_count: transactions.length,
|
|
153
|
+
cached: cacheHit,
|
|
154
|
+
cache_info: cacheHit
|
|
155
|
+
? `Data retrieved from cache for improved performance${usedDelta ? " (delta merge applied)" : ""}`
|
|
156
|
+
: "Fresh data retrieved from YNAB API",
|
|
157
|
+
transactions: transactions.map((transaction) => ({
|
|
158
|
+
id: transaction.id,
|
|
159
|
+
date: transaction.date,
|
|
160
|
+
amount: milliunitsToAmount(transaction.amount),
|
|
161
|
+
memo: transaction.memo,
|
|
162
|
+
cleared: transaction.cleared,
|
|
163
|
+
approved: transaction.approved,
|
|
164
|
+
flag_color: transaction.flag_color,
|
|
165
|
+
account_id: transaction.account_id,
|
|
166
|
+
payee_id: transaction.payee_id,
|
|
167
|
+
category_id: transaction.category_id,
|
|
168
|
+
transfer_account_id: transaction.transfer_account_id,
|
|
169
|
+
transfer_transaction_id: transaction.transfer_transaction_id,
|
|
170
|
+
matched_transaction_id: transaction.matched_transaction_id,
|
|
171
|
+
import_id: transaction.import_id,
|
|
172
|
+
deleted: transaction.deleted,
|
|
173
|
+
})),
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
"ynab:list_transactions",
|
|
180
|
+
"listing transactions",
|
|
181
|
+
errorHandler,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Handles the ynab:get_transaction tool call
|
|
187
|
+
* Gets detailed information for a specific transaction
|
|
188
|
+
*/
|
|
189
|
+
export async function handleGetTransaction(
|
|
190
|
+
ynabAPI: ynab.API,
|
|
191
|
+
params: GetTransactionParams,
|
|
192
|
+
_errorHandler?: ErrorHandler,
|
|
193
|
+
): Promise<CallToolResult> {
|
|
194
|
+
try {
|
|
195
|
+
const useCache = process.env["NODE_ENV"] !== "test";
|
|
196
|
+
|
|
197
|
+
let transaction: ynab.TransactionDetail;
|
|
198
|
+
let cacheHit = false;
|
|
199
|
+
|
|
200
|
+
if (useCache) {
|
|
201
|
+
// Use enhanced CacheManager wrap method
|
|
202
|
+
const cacheKey = CacheManager.generateKey(
|
|
203
|
+
"transaction",
|
|
204
|
+
"get",
|
|
205
|
+
params.budget_id,
|
|
206
|
+
params.transaction_id,
|
|
207
|
+
);
|
|
208
|
+
cacheHit = cacheManager.has(cacheKey);
|
|
209
|
+
transaction = await cacheManager.wrap<ynab.TransactionDetail>(cacheKey, {
|
|
210
|
+
ttl: CACHE_TTLS.TRANSACTIONS,
|
|
211
|
+
loader: async () => {
|
|
212
|
+
const response = await ynabAPI.transactions.getTransactionById(
|
|
213
|
+
params.budget_id,
|
|
214
|
+
params.transaction_id,
|
|
215
|
+
);
|
|
216
|
+
return ensureTransaction(
|
|
217
|
+
response.data.transaction,
|
|
218
|
+
"Transaction not found",
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
// Bypass cache in test environment
|
|
224
|
+
const response = await ynabAPI.transactions.getTransactionById(
|
|
225
|
+
params.budget_id,
|
|
226
|
+
params.transaction_id,
|
|
227
|
+
);
|
|
228
|
+
transaction = ensureTransaction(
|
|
229
|
+
response.data.transaction,
|
|
230
|
+
"Transaction not found",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: responseFormatter.format({
|
|
239
|
+
transaction: {
|
|
240
|
+
id: transaction.id,
|
|
241
|
+
date: transaction.date,
|
|
242
|
+
amount: milliunitsToAmount(transaction.amount),
|
|
243
|
+
memo: transaction.memo,
|
|
244
|
+
cleared: transaction.cleared,
|
|
245
|
+
approved: transaction.approved,
|
|
246
|
+
flag_color: transaction.flag_color,
|
|
247
|
+
account_id: transaction.account_id,
|
|
248
|
+
payee_id: transaction.payee_id,
|
|
249
|
+
category_id: transaction.category_id,
|
|
250
|
+
transfer_account_id: transaction.transfer_account_id,
|
|
251
|
+
transfer_transaction_id: transaction.transfer_transaction_id,
|
|
252
|
+
matched_transaction_id: transaction.matched_transaction_id,
|
|
253
|
+
import_id: transaction.import_id,
|
|
254
|
+
deleted: transaction.deleted,
|
|
255
|
+
account_name: transaction.account_name,
|
|
256
|
+
payee_name: transaction.payee_name,
|
|
257
|
+
category_name: transaction.category_name,
|
|
258
|
+
},
|
|
259
|
+
cached: cacheHit,
|
|
260
|
+
cache_info: cacheHit
|
|
261
|
+
? "Data retrieved from cache for improved performance"
|
|
262
|
+
: "Fresh data retrieved from YNAB API",
|
|
263
|
+
}),
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
};
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return handleTransactionError(error, "Failed to get transaction");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Registers read-only transaction tools with the provided registry.
|
|
274
|
+
*/
|
|
275
|
+
export function registerTransactionReadTools(
|
|
276
|
+
registry: ToolRegistry,
|
|
277
|
+
context: ToolContext,
|
|
278
|
+
): void {
|
|
279
|
+
const { adapt, adaptWithDelta } = createAdapters(context);
|
|
280
|
+
const budgetResolver = createBudgetResolver(context);
|
|
281
|
+
|
|
282
|
+
registry.register({
|
|
283
|
+
name: "list_transactions",
|
|
284
|
+
description: "List transactions for a budget with optional filtering",
|
|
285
|
+
inputSchema: ListTransactionsSchema,
|
|
286
|
+
handler: adaptWithDelta(handleListTransactions),
|
|
287
|
+
defaultArgumentResolver:
|
|
288
|
+
budgetResolver<z.infer<typeof ListTransactionsSchema>>(),
|
|
289
|
+
metadata: {
|
|
290
|
+
annotations: {
|
|
291
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
292
|
+
title: "YNAB: List Transactions",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
registry.register({
|
|
298
|
+
name: "export_transactions",
|
|
299
|
+
description:
|
|
300
|
+
"Export all transactions to a JSON file with descriptive filename",
|
|
301
|
+
inputSchema: ExportTransactionsSchema,
|
|
302
|
+
handler: adapt(handleExportTransactions),
|
|
303
|
+
defaultArgumentResolver:
|
|
304
|
+
budgetResolver<z.infer<typeof ExportTransactionsSchema>>(),
|
|
305
|
+
metadata: {
|
|
306
|
+
annotations: {
|
|
307
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
308
|
+
title: "YNAB: Export Transactions",
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
registry.register({
|
|
314
|
+
name: "get_transaction",
|
|
315
|
+
description: "Get detailed information for a specific transaction",
|
|
316
|
+
inputSchema: GetTransactionSchema,
|
|
317
|
+
handler: adapt(handleGetTransaction),
|
|
318
|
+
defaultArgumentResolver:
|
|
319
|
+
budgetResolver<z.infer<typeof GetTransactionSchema>>(),
|
|
320
|
+
metadata: {
|
|
321
|
+
annotations: {
|
|
322
|
+
...ToolAnnotationPresets.READ_ONLY_EXTERNAL,
|
|
323
|
+
title: "YNAB: Get Transaction Details",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|