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