@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,802 +1,829 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { z } from 'zod';
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
5
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from
|
|
18
|
-
import type { Tool } from
|
|
19
|
-
import * as ynab from
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
CompleteRequestSchema,
|
|
8
|
+
ErrorCode,
|
|
9
|
+
GetPromptRequestSchema,
|
|
10
|
+
ListPromptsRequestSchema,
|
|
11
|
+
ListResourceTemplatesRequestSchema,
|
|
12
|
+
ListResourcesRequestSchema,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
McpError,
|
|
15
|
+
ReadResourceRequestSchema,
|
|
16
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
+
import * as ynab from "ynab";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { registerAccountTools } from "../tools/accountTools.js";
|
|
21
|
+
import { registerBudgetTools } from "../tools/budgetTools.js";
|
|
22
|
+
import { registerCategoryTools } from "../tools/categoryTools.js";
|
|
23
|
+
import { DeltaFetcher } from "../tools/deltaFetcher.js";
|
|
24
|
+
import { registerMonthTools } from "../tools/monthTools.js";
|
|
25
|
+
import { registerPayeeTools } from "../tools/payeeTools.js";
|
|
26
|
+
import { registerReconciliationTools } from "../tools/reconciliation/index.js";
|
|
27
|
+
import { emptyObjectSchema } from "../tools/schemas/common.js";
|
|
28
|
+
import { ToolAnnotationPresets } from "../tools/toolCategories.js";
|
|
29
|
+
import { registerTransactionTools } from "../tools/transactionTools.js";
|
|
30
|
+
import { registerUtilityTools } from "../tools/utilityTools.js";
|
|
31
|
+
import { ValidationError, YNABErrorCode } from "../types/index.js";
|
|
32
|
+
import type { ToolContext } from "../types/toolRegistration.js";
|
|
20
33
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
import { PromptManager } from './prompts.js';
|
|
45
|
-
import { DiagnosticManager } from './diagnostics.js';
|
|
46
|
-
import { ServerKnowledgeStore } from './serverKnowledgeStore.js';
|
|
47
|
-
import { DeltaCache } from './deltaCache.js';
|
|
48
|
-
import { DeltaFetcher } from '../tools/deltaFetcher.js';
|
|
49
|
-
import { ToolAnnotationPresets } from '../tools/toolCategories.js';
|
|
50
|
-
import { CompletionsManager } from './completions.js';
|
|
34
|
+
AuthenticationError,
|
|
35
|
+
ValidationError as ConfigValidationError,
|
|
36
|
+
ConfigurationError,
|
|
37
|
+
} from "../utils/errors.js";
|
|
38
|
+
import { CacheManager, cacheManager } from "./cacheManager.js";
|
|
39
|
+
import { CompletionsManager } from "./completions.js";
|
|
40
|
+
import { type AppConfig, loadConfig } from "./config.js";
|
|
41
|
+
import { DeltaCache } from "./deltaCache.js";
|
|
42
|
+
import { DiagnosticManager } from "./diagnostics.js";
|
|
43
|
+
import { type ErrorHandler, createErrorHandler } from "./errorHandler.js";
|
|
44
|
+
import { PromptManager } from "./prompts.js";
|
|
45
|
+
import { ResourceManager } from "./resources.js";
|
|
46
|
+
import { responseFormatter } from "./responseFormatter.js";
|
|
47
|
+
import {
|
|
48
|
+
SecurityMiddleware,
|
|
49
|
+
withSecurityWrapper,
|
|
50
|
+
} from "./securityMiddleware.js";
|
|
51
|
+
import { ServerKnowledgeStore } from "./serverKnowledgeStore.js";
|
|
52
|
+
import {
|
|
53
|
+
type ProgressCallback,
|
|
54
|
+
type ToolDefinition,
|
|
55
|
+
ToolRegistry,
|
|
56
|
+
} from "./toolRegistry.js";
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
59
|
* YNAB MCP Server class that provides integration with You Need A Budget API
|
|
54
60
|
*/
|
|
55
61
|
export class YNABMCPServer {
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
62
|
+
private server: Server;
|
|
63
|
+
private ynabAPI: ynab.API;
|
|
64
|
+
private exitOnError: boolean;
|
|
65
|
+
private defaultBudgetId: string | undefined;
|
|
66
|
+
private configInstance: AppConfig;
|
|
67
|
+
private serverVersion: string;
|
|
68
|
+
private toolRegistry: ToolRegistry;
|
|
69
|
+
private resourceManager: ResourceManager;
|
|
70
|
+
private promptManager: PromptManager;
|
|
71
|
+
private serverKnowledgeStore: ServerKnowledgeStore;
|
|
72
|
+
private deltaCache: DeltaCache;
|
|
73
|
+
private deltaFetcher: DeltaFetcher;
|
|
74
|
+
private diagnosticManager: DiagnosticManager;
|
|
75
|
+
private errorHandler: ErrorHandler;
|
|
76
|
+
private completionsManager: CompletionsManager;
|
|
77
|
+
|
|
78
|
+
constructor(exitOnError = true) {
|
|
79
|
+
this.exitOnError = exitOnError;
|
|
80
|
+
this.configInstance = loadConfig();
|
|
81
|
+
// Config is now imported and validated at startup
|
|
82
|
+
this.defaultBudgetId = this.configInstance.YNAB_DEFAULT_BUDGET_ID;
|
|
83
|
+
|
|
84
|
+
// Initialize YNAB API
|
|
85
|
+
this.ynabAPI = new ynab.API(this.configInstance.YNAB_ACCESS_TOKEN);
|
|
86
|
+
|
|
87
|
+
// Determine server version (prefer package.json)
|
|
88
|
+
this.serverVersion = this.readPackageVersion() ?? "0.0.0";
|
|
89
|
+
|
|
90
|
+
// Initialize MCP Server
|
|
91
|
+
this.server = new Server(
|
|
92
|
+
{
|
|
93
|
+
name: "ynab-mcp-server",
|
|
94
|
+
version: this.serverVersion,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
capabilities: {
|
|
98
|
+
tools: { listChanged: false },
|
|
99
|
+
resources: {
|
|
100
|
+
subscribe: false, // YNAB API has no webhooks; subscriptions not applicable
|
|
101
|
+
listChanged: false,
|
|
102
|
+
},
|
|
103
|
+
prompts: { listChanged: false },
|
|
104
|
+
completions: {},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Create ErrorHandler instance with formatter injection
|
|
110
|
+
this.errorHandler = createErrorHandler(responseFormatter);
|
|
111
|
+
|
|
112
|
+
this.toolRegistry = new ToolRegistry({
|
|
113
|
+
withSecurityWrapper,
|
|
114
|
+
errorHandler: this.errorHandler,
|
|
115
|
+
responseFormatter,
|
|
116
|
+
cacheHelpers: {
|
|
117
|
+
generateKey: (...segments: unknown[]) => {
|
|
118
|
+
const normalized = segments.map((segment) => {
|
|
119
|
+
if (
|
|
120
|
+
typeof segment === "string" ||
|
|
121
|
+
typeof segment === "number" ||
|
|
122
|
+
typeof segment === "boolean" ||
|
|
123
|
+
segment === undefined
|
|
124
|
+
) {
|
|
125
|
+
return segment;
|
|
126
|
+
}
|
|
127
|
+
return JSON.stringify(segment);
|
|
128
|
+
}) as (string | number | boolean | undefined)[];
|
|
129
|
+
return CacheManager.generateKey("tool", ...normalized);
|
|
130
|
+
},
|
|
131
|
+
invalidate: (key: string) => {
|
|
132
|
+
try {
|
|
133
|
+
cacheManager.delete(key);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(`Failed to invalidate cache key "${key}":`, error);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
clear: () => {
|
|
139
|
+
try {
|
|
140
|
+
cacheManager.clear();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error("Failed to clear cache:", error);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
validateAccessToken: (token: string) => {
|
|
147
|
+
const expected = this.configInstance.YNAB_ACCESS_TOKEN.trim();
|
|
148
|
+
const provided = typeof token === "string" ? token.trim() : "";
|
|
149
|
+
if (!provided) {
|
|
150
|
+
throw this.errorHandler.createYNABError(
|
|
151
|
+
YNABErrorCode.UNAUTHORIZED,
|
|
152
|
+
"validating access token",
|
|
153
|
+
new Error("Missing access token"),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (provided !== expected) {
|
|
157
|
+
throw this.errorHandler.createYNABError(
|
|
158
|
+
YNABErrorCode.UNAUTHORIZED,
|
|
159
|
+
"validating access token",
|
|
160
|
+
new Error("Access token mismatch"),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Initialize service modules
|
|
167
|
+
this.resourceManager = new ResourceManager({
|
|
168
|
+
ynabAPI: this.ynabAPI,
|
|
169
|
+
responseFormatter,
|
|
170
|
+
cacheManager,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.promptManager = new PromptManager();
|
|
174
|
+
|
|
175
|
+
this.serverKnowledgeStore = new ServerKnowledgeStore();
|
|
176
|
+
this.deltaCache = new DeltaCache(cacheManager, this.serverKnowledgeStore);
|
|
177
|
+
this.deltaFetcher = new DeltaFetcher(this.ynabAPI, this.deltaCache);
|
|
178
|
+
|
|
179
|
+
this.diagnosticManager = new DiagnosticManager({
|
|
180
|
+
securityMiddleware: SecurityMiddleware,
|
|
181
|
+
cacheManager,
|
|
182
|
+
responseFormatter,
|
|
183
|
+
serverVersion: this.serverVersion,
|
|
184
|
+
serverKnowledgeStore: this.serverKnowledgeStore,
|
|
185
|
+
deltaCache: this.deltaCache,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
this.completionsManager = new CompletionsManager(
|
|
189
|
+
this.ynabAPI,
|
|
190
|
+
cacheManager,
|
|
191
|
+
() => this.defaultBudgetId,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
this.setupToolRegistry();
|
|
195
|
+
this.setupHandlers();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validates the YNAB access token by making a test API call
|
|
200
|
+
*/
|
|
201
|
+
async validateToken(): Promise<boolean> {
|
|
202
|
+
try {
|
|
203
|
+
await this.ynabAPI.user.getUser();
|
|
204
|
+
return true;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (this.isMalformedTokenResponse(error)) {
|
|
207
|
+
throw new AuthenticationError(
|
|
208
|
+
"Unexpected response from YNAB during token validation",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (error instanceof Error) {
|
|
213
|
+
// Check for authentication-related errors
|
|
214
|
+
if (
|
|
215
|
+
error.message.includes("401") ||
|
|
216
|
+
error.message.includes("Unauthorized")
|
|
217
|
+
) {
|
|
218
|
+
throw new AuthenticationError("Invalid or expired YNAB access token");
|
|
219
|
+
}
|
|
220
|
+
if (
|
|
221
|
+
error.message.includes("403") ||
|
|
222
|
+
error.message.includes("Forbidden")
|
|
223
|
+
) {
|
|
224
|
+
throw new AuthenticationError(
|
|
225
|
+
"YNAB access token has insufficient permissions",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const reason = error.message || String(error);
|
|
230
|
+
throw new AuthenticationError(`Token validation failed: ${reason}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
throw new AuthenticationError(
|
|
234
|
+
`Token validation failed: ${String(error)}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private isMalformedTokenResponse(error: unknown): boolean {
|
|
240
|
+
if (error instanceof SyntaxError) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const message =
|
|
245
|
+
typeof error === "string"
|
|
246
|
+
? error
|
|
247
|
+
: typeof (error as { message?: unknown })?.message === "string"
|
|
248
|
+
? String((error as { message: unknown }).message)
|
|
249
|
+
: null;
|
|
250
|
+
|
|
251
|
+
if (!message) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const normalized = message.toLowerCase();
|
|
256
|
+
return (
|
|
257
|
+
normalized.includes("unexpected token") ||
|
|
258
|
+
normalized.includes("unexpected end of json") ||
|
|
259
|
+
normalized.includes("<html")
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Sets up MCP server request handlers
|
|
265
|
+
*/
|
|
266
|
+
private setupHandlers(): void {
|
|
267
|
+
// Handle list resources requests
|
|
268
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
269
|
+
return this.resourceManager.listResources();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Handle list resource templates requests
|
|
273
|
+
this.server.setRequestHandler(
|
|
274
|
+
ListResourceTemplatesRequestSchema,
|
|
275
|
+
async () => {
|
|
276
|
+
return this.resourceManager.listResourceTemplates();
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Handle read resource requests
|
|
281
|
+
this.server.setRequestHandler(
|
|
282
|
+
ReadResourceRequestSchema,
|
|
283
|
+
async (request) => {
|
|
284
|
+
const { uri } = request.params;
|
|
285
|
+
return await this.resourceManager.readResource(uri);
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Handle list prompts requests
|
|
290
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
291
|
+
return this.promptManager.listPrompts();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Handle get prompt requests
|
|
295
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
296
|
+
const { name, arguments: args } = request.params;
|
|
297
|
+
const result = await this.promptManager.getPrompt(name, args);
|
|
298
|
+
// The SDK expects the result to match the protocol's PromptResponse shape
|
|
299
|
+
return result as unknown as { description?: string; messages: unknown[] };
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Handle list tools requests
|
|
303
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
304
|
+
return {
|
|
305
|
+
tools: this.toolRegistry.listTools(),
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Handle tool call requests
|
|
310
|
+
this.server.setRequestHandler(
|
|
311
|
+
CallToolRequestSchema,
|
|
312
|
+
async (request, extra) => {
|
|
313
|
+
if (!this.toolRegistry.hasTool(request.params.name)) {
|
|
314
|
+
throw new McpError(
|
|
315
|
+
ErrorCode.InvalidParams,
|
|
316
|
+
`Unknown tool: ${request.params.name}`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const rawArgs = (request.params.arguments ?? undefined) as
|
|
320
|
+
| Record<string, unknown>
|
|
321
|
+
| undefined;
|
|
322
|
+
const minifyOverride = this.extractMinifyOverride(rawArgs);
|
|
323
|
+
|
|
324
|
+
const sanitizedArgs = rawArgs
|
|
325
|
+
? (() => {
|
|
326
|
+
const clone: Record<string, unknown> = { ...rawArgs };
|
|
327
|
+
clone["minify"] = undefined;
|
|
328
|
+
clone["_minify"] = undefined;
|
|
329
|
+
clone["__minify"] = undefined;
|
|
330
|
+
return clone;
|
|
331
|
+
})()
|
|
332
|
+
: undefined;
|
|
333
|
+
|
|
334
|
+
const executionOptions: {
|
|
335
|
+
name: string;
|
|
336
|
+
accessToken: string;
|
|
337
|
+
arguments: Record<string, unknown>;
|
|
338
|
+
minifyOverride?: boolean;
|
|
339
|
+
sendProgress?: ProgressCallback;
|
|
340
|
+
} = {
|
|
341
|
+
name: request.params.name,
|
|
342
|
+
accessToken: this.configInstance.YNAB_ACCESS_TOKEN,
|
|
343
|
+
arguments: sanitizedArgs ?? {},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (minifyOverride !== undefined) {
|
|
347
|
+
executionOptions.minifyOverride = minifyOverride;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Create progress callback if client provided a progressToken
|
|
351
|
+
const progressToken = (
|
|
352
|
+
request.params as { _meta?: { progressToken?: string | number } }
|
|
353
|
+
)._meta?.progressToken;
|
|
354
|
+
if (progressToken !== undefined && extra.sendNotification) {
|
|
355
|
+
executionOptions.sendProgress = async (params) => {
|
|
356
|
+
try {
|
|
357
|
+
await extra.sendNotification({
|
|
358
|
+
method: "notifications/progress",
|
|
359
|
+
params: {
|
|
360
|
+
progressToken,
|
|
361
|
+
progress: params.progress,
|
|
362
|
+
...(params.total !== undefined && { total: params.total }),
|
|
363
|
+
...(params.message !== undefined && {
|
|
364
|
+
message: params.message,
|
|
365
|
+
}),
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
} catch {
|
|
369
|
+
// Progress notifications are non-critical; allow tool execution to continue.
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return await this.toolRegistry.executeTool(executionOptions);
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Handle completion requests for autocomplete
|
|
379
|
+
this.server.setRequestHandler(CompleteRequestSchema, async (request) => {
|
|
380
|
+
const { argument, context } = request.params;
|
|
381
|
+
|
|
382
|
+
// Get completions from the manager, handling optional context
|
|
383
|
+
const completionContext = context?.arguments
|
|
384
|
+
? { arguments: context.arguments }
|
|
385
|
+
: undefined;
|
|
386
|
+
const result = await this.completionsManager.getCompletions(
|
|
387
|
+
argument.name,
|
|
388
|
+
argument.value,
|
|
389
|
+
completionContext,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// Return in MCP-compliant format
|
|
393
|
+
return {
|
|
394
|
+
completion: result.completion,
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Registers all tools with the registry to centralize handler execution
|
|
401
|
+
*/
|
|
402
|
+
private setupToolRegistry(): void {
|
|
403
|
+
const register = <TInput extends Record<string, unknown>>(
|
|
404
|
+
definition: ToolDefinition<TInput>,
|
|
405
|
+
): void => {
|
|
406
|
+
this.toolRegistry.register(definition);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const toolContext: ToolContext = {
|
|
410
|
+
ynabAPI: this.ynabAPI,
|
|
411
|
+
deltaFetcher: this.deltaFetcher,
|
|
412
|
+
deltaCache: this.deltaCache,
|
|
413
|
+
serverKnowledgeStore: this.serverKnowledgeStore,
|
|
414
|
+
getDefaultBudgetId: () => this.defaultBudgetId,
|
|
415
|
+
setDefaultBudget: (budgetId: string) => this.setDefaultBudget(budgetId),
|
|
416
|
+
cacheManager,
|
|
417
|
+
errorHandler: this.errorHandler,
|
|
418
|
+
diagnosticManager: this.diagnosticManager,
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const setDefaultBudgetSchema = z
|
|
422
|
+
.object({ budget_id: z.string().min(1) })
|
|
423
|
+
.strict();
|
|
424
|
+
const diagnosticInfoSchema = z
|
|
425
|
+
.object({
|
|
426
|
+
include_memory: z.boolean().default(true),
|
|
427
|
+
include_environment: z.boolean().default(true),
|
|
428
|
+
include_server: z.boolean().default(true),
|
|
429
|
+
include_security: z.boolean().default(true),
|
|
430
|
+
include_cache: z.boolean().default(true),
|
|
431
|
+
include_delta: z.boolean().default(true),
|
|
432
|
+
})
|
|
433
|
+
.strict();
|
|
434
|
+
const setOutputFormatSchema = z
|
|
435
|
+
.object({
|
|
436
|
+
default_minify: z.boolean().optional(),
|
|
437
|
+
pretty_spaces: z.number().int().min(0).max(10).optional(),
|
|
438
|
+
})
|
|
439
|
+
.strict();
|
|
440
|
+
registerBudgetTools(this.toolRegistry, toolContext);
|
|
441
|
+
registerPayeeTools(this.toolRegistry, toolContext);
|
|
442
|
+
registerCategoryTools(this.toolRegistry, toolContext);
|
|
443
|
+
registerAccountTools(this.toolRegistry, toolContext);
|
|
444
|
+
registerMonthTools(this.toolRegistry, toolContext);
|
|
445
|
+
registerTransactionTools(this.toolRegistry, toolContext);
|
|
446
|
+
registerReconciliationTools(this.toolRegistry, toolContext);
|
|
447
|
+
registerUtilityTools(this.toolRegistry, toolContext);
|
|
448
|
+
|
|
449
|
+
// Server-owned inline tools stay here because they depend on instance state (default budget,
|
|
450
|
+
// diagnostics manager, cache manager, response formatter) rather than the factory context.
|
|
451
|
+
register({
|
|
452
|
+
name: "set_default_budget",
|
|
453
|
+
description: "Set the default budget for subsequent operations",
|
|
454
|
+
inputSchema: setDefaultBudgetSchema,
|
|
455
|
+
handler: async ({ input }) => {
|
|
456
|
+
const { budget_id } = input;
|
|
457
|
+
await this.ynabAPI.budgets.getBudgetById(budget_id);
|
|
458
|
+
this.setDefaultBudget(budget_id);
|
|
459
|
+
|
|
460
|
+
// Cache warming for frequently accessed data (fire-and-forget)
|
|
461
|
+
this.warmCacheForBudget(budget_id).catch(() => {
|
|
462
|
+
// Silently handle cache warming errors to not affect main operation
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: responseFormatter.format({
|
|
470
|
+
success: true,
|
|
471
|
+
message: `Default budget set to: ${budget_id}`,
|
|
472
|
+
default_budget_id: budget_id,
|
|
473
|
+
cache_warm_started: true,
|
|
474
|
+
}),
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
};
|
|
478
|
+
},
|
|
479
|
+
metadata: {
|
|
480
|
+
annotations: {
|
|
481
|
+
...ToolAnnotationPresets.WRITE_EXTERNAL_UPDATE,
|
|
482
|
+
title: "YNAB: Set Default Budget",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
register({
|
|
488
|
+
name: "get_default_budget",
|
|
489
|
+
description: "Get the currently set default budget",
|
|
490
|
+
inputSchema: emptyObjectSchema,
|
|
491
|
+
handler: async () => {
|
|
492
|
+
try {
|
|
493
|
+
const defaultBudget = this.getDefaultBudget();
|
|
494
|
+
return {
|
|
495
|
+
content: [
|
|
496
|
+
{
|
|
497
|
+
type: "text",
|
|
498
|
+
text: responseFormatter.format({
|
|
499
|
+
default_budget_id: defaultBudget ?? null,
|
|
500
|
+
has_default: !!defaultBudget,
|
|
501
|
+
message: defaultBudget
|
|
502
|
+
? `Default budget is set to: ${defaultBudget}`
|
|
503
|
+
: "No default budget is currently set",
|
|
504
|
+
}),
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
} catch (error) {
|
|
509
|
+
return this.errorHandler.createValidationError(
|
|
510
|
+
"Error getting default budget",
|
|
511
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
metadata: {
|
|
516
|
+
annotations: {
|
|
517
|
+
// Intentionally categorized as UTILITY_LOCAL (not READ_ONLY_EXTERNAL) because
|
|
518
|
+
// this tool only reads local server state without making any YNAB API calls.
|
|
519
|
+
// Compare with set_default_budget which calls ynabAPI.budgets.getBudgetById().
|
|
520
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
521
|
+
title: "YNAB: Get Default Budget",
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
register({
|
|
527
|
+
name: "diagnostic_info",
|
|
528
|
+
description:
|
|
529
|
+
"Get comprehensive diagnostic information about the MCP server",
|
|
530
|
+
inputSchema: diagnosticInfoSchema,
|
|
531
|
+
handler: async ({ input }) => {
|
|
532
|
+
return this.diagnosticManager.collectDiagnostics(input);
|
|
533
|
+
},
|
|
534
|
+
metadata: {
|
|
535
|
+
annotations: {
|
|
536
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
537
|
+
title: "YNAB: Diagnostic Information",
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
register({
|
|
543
|
+
name: "clear_cache",
|
|
544
|
+
description: "Clear the in-memory cache (safe, no YNAB data is modified)",
|
|
545
|
+
inputSchema: emptyObjectSchema,
|
|
546
|
+
handler: async () => {
|
|
547
|
+
cacheManager.clear();
|
|
548
|
+
return {
|
|
549
|
+
content: [
|
|
550
|
+
{ type: "text", text: responseFormatter.format({ success: true }) },
|
|
551
|
+
],
|
|
552
|
+
};
|
|
553
|
+
},
|
|
554
|
+
metadata: {
|
|
555
|
+
annotations: {
|
|
556
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
557
|
+
title: "YNAB: Clear Cache",
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
register({
|
|
563
|
+
name: "set_output_format",
|
|
564
|
+
description:
|
|
565
|
+
"Configure default JSON output formatting (minify or pretty spaces)",
|
|
566
|
+
inputSchema: setOutputFormatSchema,
|
|
567
|
+
handler: async ({ input }) => {
|
|
568
|
+
const options: { defaultMinify?: boolean; prettySpaces?: number } = {};
|
|
569
|
+
if (typeof input.default_minify === "boolean") {
|
|
570
|
+
options.defaultMinify = input.default_minify;
|
|
571
|
+
}
|
|
572
|
+
if (typeof input.pretty_spaces === "number") {
|
|
573
|
+
options.prettySpaces = Math.max(
|
|
574
|
+
0,
|
|
575
|
+
Math.min(10, Math.floor(input.pretty_spaces)),
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
responseFormatter.configure(options);
|
|
579
|
+
|
|
580
|
+
// Build human-readable message describing the new configuration
|
|
581
|
+
const parts: string[] = [];
|
|
582
|
+
if (options.defaultMinify !== undefined) {
|
|
583
|
+
parts.push(`minify=${options.defaultMinify}`);
|
|
584
|
+
}
|
|
585
|
+
if (options.prettySpaces !== undefined) {
|
|
586
|
+
parts.push(`spaces=${options.prettySpaces}`);
|
|
587
|
+
}
|
|
588
|
+
const message =
|
|
589
|
+
parts.length > 0
|
|
590
|
+
? `Output format configured: ${parts.join(", ")}`
|
|
591
|
+
: "Output format configured";
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
content: [
|
|
595
|
+
{
|
|
596
|
+
type: "text",
|
|
597
|
+
text: responseFormatter.format({
|
|
598
|
+
success: true,
|
|
599
|
+
message,
|
|
600
|
+
options,
|
|
601
|
+
}),
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
};
|
|
605
|
+
},
|
|
606
|
+
metadata: {
|
|
607
|
+
annotations: {
|
|
608
|
+
...ToolAnnotationPresets.UTILITY_LOCAL,
|
|
609
|
+
title: "YNAB: Set Output Format",
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private extractMinifyOverride(
|
|
616
|
+
args: Record<string, unknown> | undefined,
|
|
617
|
+
): boolean | undefined {
|
|
618
|
+
if (!args) {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
for (const key of ["minify", "_minify", "__minify"] as const) {
|
|
623
|
+
const value = args[key];
|
|
624
|
+
if (typeof value === "boolean") {
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Starts the MCP server with stdio transport
|
|
634
|
+
*/
|
|
635
|
+
async run(): Promise<void> {
|
|
636
|
+
try {
|
|
637
|
+
// Validate token before starting server
|
|
638
|
+
await this.validateToken();
|
|
639
|
+
|
|
640
|
+
const transport = new StdioServerTransport();
|
|
641
|
+
await this.server.connect(transport);
|
|
642
|
+
|
|
643
|
+
console.error("YNAB MCP Server started successfully");
|
|
644
|
+
} catch (error) {
|
|
645
|
+
if (
|
|
646
|
+
error instanceof AuthenticationError ||
|
|
647
|
+
error instanceof ConfigurationError ||
|
|
648
|
+
error instanceof ConfigValidationError ||
|
|
649
|
+
error instanceof ValidationError ||
|
|
650
|
+
(error as { name?: string })?.name === "ValidationError"
|
|
651
|
+
) {
|
|
652
|
+
console.error(
|
|
653
|
+
`Server startup failed: ${error instanceof Error ? error.message : error}`,
|
|
654
|
+
);
|
|
655
|
+
if (this.exitOnError) {
|
|
656
|
+
process.exit(1);
|
|
657
|
+
} else {
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
throw error;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Gets the YNAB API instance (for testing purposes)
|
|
667
|
+
*/
|
|
668
|
+
getYNABAPI(): ynab.API {
|
|
669
|
+
return this.ynabAPI;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Gets the MCP server instance (for testing purposes)
|
|
674
|
+
*/
|
|
675
|
+
getServer(): Server {
|
|
676
|
+
return this.server;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Sets the default budget ID for operations
|
|
681
|
+
*/
|
|
682
|
+
setDefaultBudget(budgetId: string): void {
|
|
683
|
+
this.defaultBudgetId = budgetId;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Gets the default budget ID
|
|
688
|
+
*/
|
|
689
|
+
getDefaultBudget(): string | undefined {
|
|
690
|
+
return this.defaultBudgetId;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Clears the default budget ID (primarily for testing purposes)
|
|
695
|
+
*/
|
|
696
|
+
clearDefaultBudget(): void {
|
|
697
|
+
this.defaultBudgetId = undefined;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Gets the tool registry instance (for testing purposes)
|
|
702
|
+
*/
|
|
703
|
+
getToolRegistry(): ToolRegistry {
|
|
704
|
+
return this.toolRegistry;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Warm cache for frequently accessed data after setting default budget
|
|
709
|
+
* Uses fire-and-forget pattern to avoid blocking the main operation
|
|
710
|
+
* Runs cache warming operations in parallel for faster completion
|
|
711
|
+
*/
|
|
712
|
+
private async warmCacheForBudget(budgetId: string): Promise<void> {
|
|
713
|
+
try {
|
|
714
|
+
// Run all cache warming operations in parallel
|
|
715
|
+
await Promise.all([
|
|
716
|
+
this.deltaFetcher.fetchAccounts(budgetId, { forceFullRefresh: true }),
|
|
717
|
+
this.deltaFetcher.fetchCategories(budgetId, { forceFullRefresh: true }),
|
|
718
|
+
this.deltaFetcher.fetchPayees(budgetId, { forceFullRefresh: true }),
|
|
719
|
+
]);
|
|
720
|
+
} catch {
|
|
721
|
+
// Cache warming failures should not affect the main operation
|
|
722
|
+
// Errors are handled by the caller with a catch block
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Public handler methods for testing and external access
|
|
728
|
+
*/
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Handle list tools request - public method for testing
|
|
732
|
+
*/
|
|
733
|
+
public async handleListTools() {
|
|
734
|
+
return {
|
|
735
|
+
tools: this.toolRegistry.listTools(),
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Handle list resources request - public method for testing
|
|
741
|
+
*/
|
|
742
|
+
public async handleListResources() {
|
|
743
|
+
return this.resourceManager.listResources();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Handle read resource request - public method for testing
|
|
748
|
+
*/
|
|
749
|
+
public async handleReadResource(params: { uri: string }) {
|
|
750
|
+
const { uri } = params;
|
|
751
|
+
try {
|
|
752
|
+
return await this.resourceManager.readResource(uri);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
return this.errorHandler.handleError(error, `reading resource: ${uri}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Handle list prompts request - public method for testing
|
|
760
|
+
*/
|
|
761
|
+
public async handleListPrompts() {
|
|
762
|
+
return this.promptManager.listPrompts();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Handle get prompt request - public method for testing
|
|
767
|
+
*/
|
|
768
|
+
public async handleGetPrompt(params: {
|
|
769
|
+
name: string;
|
|
770
|
+
arguments?: Record<string, unknown>;
|
|
771
|
+
}) {
|
|
772
|
+
const { name, arguments: args } = params;
|
|
773
|
+
try {
|
|
774
|
+
const prompt = await this.promptManager.getPrompt(name, args);
|
|
775
|
+
const tools = Array.isArray((prompt as { tools?: unknown[] }).tools)
|
|
776
|
+
? ((prompt as { tools?: unknown[] }).tools as Tool[])
|
|
777
|
+
: undefined;
|
|
778
|
+
return tools ? { ...prompt, tools } : prompt;
|
|
779
|
+
} catch (error) {
|
|
780
|
+
return this.errorHandler.handleError(error, `getting prompt: ${name}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Try to read the package version for accurate server metadata
|
|
786
|
+
*/
|
|
787
|
+
private readPackageVersion(): string | null {
|
|
788
|
+
const candidates = [path.resolve(process.cwd(), "package.json")];
|
|
789
|
+
try {
|
|
790
|
+
// May fail in bundled CJS builds; guard accordingly
|
|
791
|
+
const metaUrl = (import.meta as unknown as { url?: string })?.url;
|
|
792
|
+
if (metaUrl) {
|
|
793
|
+
const maybe = path.resolve(
|
|
794
|
+
path.dirname(new URL(metaUrl).pathname),
|
|
795
|
+
"../../package.json",
|
|
796
|
+
);
|
|
797
|
+
candidates.push(maybe);
|
|
798
|
+
}
|
|
799
|
+
} catch {
|
|
800
|
+
// ignore
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
// CJS bundles can rely on __dirname being defined; add nearby package.json fallbacks
|
|
804
|
+
const dir = typeof __dirname === "string" ? __dirname : undefined;
|
|
805
|
+
if (dir) {
|
|
806
|
+
candidates.push(
|
|
807
|
+
path.resolve(dir, "../../package.json"),
|
|
808
|
+
path.resolve(dir, "../package.json"),
|
|
809
|
+
path.resolve(dir, "package.json"),
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
} catch {
|
|
813
|
+
// ignore additional fallbacks
|
|
814
|
+
}
|
|
815
|
+
for (const p of candidates) {
|
|
816
|
+
try {
|
|
817
|
+
if (fs.existsSync(p)) {
|
|
818
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
819
|
+
const pkg = JSON.parse(raw) as { version?: string };
|
|
820
|
+
if (pkg.version && typeof pkg.version === "string")
|
|
821
|
+
return pkg.version;
|
|
822
|
+
}
|
|
823
|
+
} catch {
|
|
824
|
+
// ignore and try next
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
802
829
|
}
|