@dizzlkheinz/ynab-mcpb 0.18.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +87 -8
- package/bin/ynab-mcp-server.cjs +2 -2
- package/bin/ynab-mcp-server.js +3 -3
- package/biome.json +39 -0
- package/dist/bundle/index.cjs +67 -67
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -27
- package/dist/server/YNABMCPServer.d.ts +3 -4
- package/dist/server/YNABMCPServer.js +111 -116
- package/dist/server/budgetResolver.d.ts +6 -5
- package/dist/server/budgetResolver.js +46 -36
- package/dist/server/cacheKeys.js +6 -6
- package/dist/server/cacheManager.js +14 -11
- package/dist/server/completions.d.ts +2 -2
- package/dist/server/completions.js +20 -15
- package/dist/server/config.d.ts +10 -5
- package/dist/server/config.js +24 -7
- package/dist/server/deltaCache.d.ts +2 -2
- package/dist/server/deltaCache.js +22 -16
- package/dist/server/deltaCache.merge.d.ts +2 -2
- package/dist/server/diagnostics.d.ts +4 -4
- package/dist/server/diagnostics.js +38 -32
- package/dist/server/errorHandler.d.ts +5 -12
- package/dist/server/errorHandler.js +219 -217
- package/dist/server/prompts.d.ts +2 -2
- package/dist/server/prompts.js +45 -45
- package/dist/server/rateLimiter.js +4 -4
- package/dist/server/requestLogger.d.ts +1 -1
- package/dist/server/requestLogger.js +40 -35
- package/dist/server/resources.d.ts +3 -3
- package/dist/server/resources.js +55 -52
- package/dist/server/responseFormatter.js +6 -6
- package/dist/server/securityMiddleware.d.ts +2 -2
- package/dist/server/securityMiddleware.js +22 -20
- package/dist/server/serverKnowledgeStore.js +1 -1
- package/dist/server/toolRegistry.d.ts +3 -3
- package/dist/server/toolRegistry.js +47 -40
- package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
- package/dist/tools/__tests__/deltaTestUtils.js +2 -2
- package/dist/tools/accountTools.d.ts +9 -8
- package/dist/tools/accountTools.js +47 -47
- package/dist/tools/adapters.d.ts +13 -8
- package/dist/tools/adapters.js +21 -11
- package/dist/tools/budgetTools.d.ts +8 -7
- package/dist/tools/budgetTools.js +22 -22
- package/dist/tools/categoryTools.d.ts +9 -8
- package/dist/tools/categoryTools.js +68 -59
- package/dist/tools/compareTransactions/formatter.d.ts +3 -3
- package/dist/tools/compareTransactions/formatter.js +9 -9
- package/dist/tools/compareTransactions/index.d.ts +6 -6
- package/dist/tools/compareTransactions/index.js +58 -43
- package/dist/tools/compareTransactions/matcher.d.ts +1 -1
- package/dist/tools/compareTransactions/matcher.js +28 -15
- package/dist/tools/compareTransactions/parser.d.ts +2 -2
- package/dist/tools/compareTransactions/parser.js +144 -138
- package/dist/tools/compareTransactions/types.d.ts +4 -4
- package/dist/tools/compareTransactions.d.ts +1 -1
- package/dist/tools/compareTransactions.js +1 -1
- package/dist/tools/deltaFetcher.d.ts +2 -2
- package/dist/tools/deltaFetcher.js +16 -15
- package/dist/tools/deltaSupport.d.ts +4 -4
- package/dist/tools/deltaSupport.js +35 -41
- package/dist/tools/exportTransactions.d.ts +5 -4
- package/dist/tools/exportTransactions.js +61 -59
- package/dist/tools/monthTools.d.ts +7 -6
- package/dist/tools/monthTools.js +31 -29
- package/dist/tools/payeeTools.d.ts +7 -6
- package/dist/tools/payeeTools.js +28 -28
- package/dist/tools/reconcileAdapter.d.ts +2 -2
- package/dist/tools/reconcileAdapter.js +21 -11
- package/dist/tools/reconciliation/analyzer.d.ts +4 -4
- package/dist/tools/reconciliation/analyzer.js +136 -57
- package/dist/tools/reconciliation/csvParser.d.ts +3 -3
- package/dist/tools/reconciliation/csvParser.js +128 -104
- package/dist/tools/reconciliation/executor.d.ts +4 -4
- package/dist/tools/reconciliation/executor.js +148 -109
- package/dist/tools/reconciliation/index.d.ts +10 -10
- package/dist/tools/reconciliation/index.js +96 -83
- package/dist/tools/reconciliation/matcher.d.ts +3 -3
- package/dist/tools/reconciliation/matcher.js +17 -16
- package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
- package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
- package/dist/tools/reconciliation/recommendationEngine.js +40 -40
- package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
- package/dist/tools/reconciliation/reportFormatter.js +79 -54
- package/dist/tools/reconciliation/signDetector.d.ts +1 -1
- package/dist/tools/reconciliation/types.d.ts +19 -16
- package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
- package/dist/tools/schemas/common.d.ts +1 -1
- package/dist/tools/schemas/common.js +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
- package/dist/tools/schemas/outputs/index.d.ts +14 -14
- package/dist/tools/schemas/outputs/index.js +14 -14
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
- package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
- package/dist/tools/schemas/shared/commonOutputs.js +15 -9
- package/dist/tools/transactionReadTools.d.ts +11 -0
- package/dist/tools/transactionReadTools.js +202 -0
- package/dist/tools/transactionSchemas.d.ts +309 -0
- package/dist/tools/transactionSchemas.js +235 -0
- package/dist/tools/transactionTools.d.ts +6 -302
- package/dist/tools/transactionTools.js +7 -2054
- package/dist/tools/transactionUtils.d.ts +31 -0
- package/dist/tools/transactionUtils.js +364 -0
- package/dist/tools/transactionWriteTools.d.ts +20 -0
- package/dist/tools/transactionWriteTools.js +1342 -0
- package/dist/tools/utilityTools.d.ts +5 -4
- package/dist/tools/utilityTools.js +11 -11
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +6 -6
- package/dist/types/reconciliation.d.ts +1 -1
- package/dist/types/toolRegistration.d.ts +14 -12
- package/dist/utils/amountUtils.js +1 -1
- package/dist/utils/dateUtils.js +4 -4
- package/dist/utils/errors.d.ts +3 -3
- package/dist/utils/errors.js +4 -4
- package/dist/utils/money.d.ts +2 -2
- package/dist/utils/money.js +8 -8
- package/dist/utils/validationError.d.ts +1 -1
- package/dist/utils/validationError.js +1 -1
- package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
- package/docs/assets/schemas/reconciliation-v2.json +360 -336
- package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
- package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
- package/esbuild.config.mjs +53 -50
- package/meta.json +12548 -12548
- package/package.json +98 -109
- package/scripts/analyze-bundle.mjs +33 -30
- package/scripts/create-pr-description.js +169 -120
- package/scripts/run-all-tests.js +205 -0
- package/scripts/run-domain-integration-tests.js +28 -18
- package/scripts/run-generate-mcpb.js +19 -17
- package/scripts/run-throttled-integration-tests.js +92 -83
- package/scripts/test-delta-params.mjs +149 -120
- package/scripts/test-recommendations.ts +36 -32
- package/scripts/tmpTransaction.ts +80 -43
- package/scripts/validate-env.js +98 -91
- package/scripts/verify-build.js +78 -76
- package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
- package/src/__tests__/performance.test.ts +723 -671
- package/src/__tests__/setup.ts +442 -395
- package/src/__tests__/smoke.e2e.test.ts +41 -39
- package/src/__tests__/testRunner.ts +314 -295
- package/src/__tests__/testUtils.ts +456 -364
- package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
- package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
- package/src/index.ts +68 -59
- package/src/server/CLAUDE.md +480 -0
- package/src/server/YNABMCPServer.ts +821 -794
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
- package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
- package/src/server/__tests__/budgetResolver.test.ts +466 -423
- package/src/server/__tests__/cacheManager.test.ts +891 -874
- package/src/server/__tests__/completions.integration.test.ts +115 -106
- package/src/server/__tests__/completions.test.ts +334 -313
- package/src/server/__tests__/config.test.ts +98 -86
- package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
- package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
- package/src/server/__tests__/deltaCache.test.ts +946 -759
- package/src/server/__tests__/diagnostics.test.ts +825 -792
- package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
- package/src/server/__tests__/errorHandler.test.ts +402 -397
- package/src/server/__tests__/prompts.test.ts +424 -347
- package/src/server/__tests__/rateLimiter.test.ts +313 -309
- package/src/server/__tests__/requestLogger.test.ts +443 -403
- package/src/server/__tests__/resources.template.test.ts +196 -185
- package/src/server/__tests__/resources.test.ts +294 -288
- package/src/server/__tests__/security.integration.test.ts +487 -421
- package/src/server/__tests__/securityMiddleware.test.ts +519 -444
- package/src/server/__tests__/server-startup.integration.test.ts +509 -490
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
- package/src/server/__tests__/toolRegistration.test.ts +239 -210
- package/src/server/__tests__/toolRegistry.test.ts +907 -845
- package/src/server/budgetResolver.ts +221 -181
- package/src/server/cacheKeys.ts +6 -6
- package/src/server/cacheManager.ts +498 -484
- package/src/server/completions.ts +267 -243
- package/src/server/config.ts +35 -14
- package/src/server/deltaCache.merge.ts +146 -128
- package/src/server/deltaCache.ts +352 -309
- package/src/server/diagnostics.ts +257 -242
- package/src/server/errorHandler.ts +747 -744
- package/src/server/prompts.ts +181 -176
- package/src/server/rateLimiter.ts +131 -129
- package/src/server/requestLogger.ts +350 -322
- package/src/server/resources.ts +442 -374
- package/src/server/responseFormatter.ts +41 -37
- package/src/server/securityMiddleware.ts +223 -205
- package/src/server/serverKnowledgeStore.ts +67 -67
- package/src/server/toolRegistry.ts +508 -474
- package/src/tools/CLAUDE.md +604 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
- package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
- package/src/tools/__tests__/accountTools.test.ts +685 -638
- package/src/tools/__tests__/adapters.test.ts +142 -108
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
- package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
- package/src/tools/__tests__/budgetTools.test.ts +442 -413
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
- package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
- package/src/tools/__tests__/categoryTools.test.ts +656 -625
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
- package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
- package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
- package/src/tools/__tests__/compareTransactions.test.ts +352 -332
- package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
- package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
- package/src/tools/__tests__/deltaSupport.test.ts +211 -184
- package/src/tools/__tests__/deltaTestUtils.ts +37 -33
- package/src/tools/__tests__/exportTransactions.test.ts +205 -200
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
- package/src/tools/__tests__/monthTools.test.ts +561 -512
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
- package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
- package/src/tools/__tests__/payeeTools.test.ts +486 -434
- package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
- package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
- package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
- package/src/tools/__tests__/utilityTools.test.ts +68 -58
- package/src/tools/accountTools.ts +293 -271
- package/src/tools/adapters.ts +120 -63
- package/src/tools/budgetTools.ts +121 -116
- package/src/tools/categoryTools.ts +379 -339
- package/src/tools/compareTransactions/formatter.ts +131 -119
- package/src/tools/compareTransactions/index.ts +249 -214
- package/src/tools/compareTransactions/matcher.ts +259 -209
- package/src/tools/compareTransactions/parser.ts +517 -487
- package/src/tools/compareTransactions/types.ts +38 -38
- package/src/tools/compareTransactions.ts +1 -1
- package/src/tools/deltaFetcher.ts +281 -260
- package/src/tools/deltaSupport.ts +264 -259
- package/src/tools/exportTransactions.ts +230 -218
- package/src/tools/monthTools.ts +180 -165
- package/src/tools/payeeTools.ts +152 -140
- package/src/tools/reconcileAdapter.ts +297 -246
- package/src/tools/reconciliation/CLAUDE.md +506 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
- package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
- package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
- package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
- package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
- package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
- package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
- package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
- package/src/tools/reconciliation/analyzer.ts +582 -406
- package/src/tools/reconciliation/csvParser.ts +656 -609
- package/src/tools/reconciliation/executor.ts +1290 -1128
- package/src/tools/reconciliation/index.ts +580 -528
- package/src/tools/reconciliation/matcher.ts +256 -240
- package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
- package/src/tools/reconciliation/recommendationEngine.ts +357 -345
- package/src/tools/reconciliation/reportFormatter.ts +349 -276
- package/src/tools/reconciliation/signDetector.ts +89 -83
- package/src/tools/reconciliation/types.ts +164 -153
- package/src/tools/reconciliation/ynabAdapter.ts +17 -15
- package/src/tools/schemas/CLAUDE.md +546 -0
- package/src/tools/schemas/common.ts +1 -1
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
- package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
- package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
- package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
- package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
- package/src/tools/schemas/outputs/index.ts +163 -163
- package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
- package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
- package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
- package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
- package/src/tools/schemas/shared/commonOutputs.ts +27 -19
- package/src/tools/toolCategories.ts +114 -114
- package/src/tools/transactionReadTools.ts +327 -0
- package/src/tools/transactionSchemas.ts +484 -0
- package/src/tools/transactionTools.ts +107 -2990
- package/src/tools/transactionUtils.ts +621 -0
- package/src/tools/transactionWriteTools.ts +2110 -0
- package/src/tools/utilityTools.ts +46 -41
- package/src/types/CLAUDE.md +477 -0
- package/src/types/__tests__/index.test.ts +51 -51
- package/src/types/index.ts +43 -39
- package/src/types/integration-tests.d.ts +26 -26
- package/src/types/reconciliation.ts +29 -29
- package/src/types/toolAnnotations.ts +30 -30
- package/src/types/toolRegistration.ts +43 -32
- package/src/utils/CLAUDE.md +508 -0
- package/src/utils/__tests__/dateUtils.test.ts +174 -168
- package/src/utils/__tests__/money.test.ts +193 -187
- package/src/utils/amountUtils.ts +5 -5
- package/src/utils/baseError.ts +5 -5
- package/src/utils/dateUtils.ts +29 -26
- package/src/utils/errors.ts +14 -14
- package/src/utils/money.ts +66 -52
- package/src/utils/validationError.ts +1 -1
- package/tsconfig.json +29 -29
- package/tsconfig.prod.json +16 -16
- package/vitest-reporters/split-json-reporter.ts +247 -204
- package/vitest.config.ts +99 -95
- package/.prettierignore +0 -10
- package/.prettierrc.json +0 -10
- package/eslint.config.js +0 -49
|
@@ -1,57 +1,63 @@
|
|
|
1
|
-
import type { CallToolResult, Tool } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { MCPToolAnnotations } from
|
|
1
|
+
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { fromZodError } from "zod-validation-error";
|
|
3
|
+
import { toJSONSchema, z } from "zod/v4";
|
|
4
|
+
import type { MCPToolAnnotations } from "../types/toolAnnotations.js";
|
|
5
5
|
|
|
6
6
|
export type SecurityWrapperFactory = <T extends Record<string, unknown>>(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
namespace: string,
|
|
8
|
+
operation: string,
|
|
9
|
+
schema: z.ZodSchema<T>,
|
|
10
10
|
) => (
|
|
11
|
-
|
|
11
|
+
accessToken: string,
|
|
12
12
|
) => (
|
|
13
|
-
|
|
14
|
-
) => (
|
|
13
|
+
params: Record<string, unknown>,
|
|
14
|
+
) => (
|
|
15
|
+
handler: (validated: T) => Promise<CallToolResult>,
|
|
16
|
+
) => Promise<CallToolResult>;
|
|
15
17
|
|
|
16
18
|
export interface ErrorHandlerContract {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
handleError(error: unknown, context: string): CallToolResult;
|
|
20
|
+
createValidationError(
|
|
21
|
+
message: string,
|
|
22
|
+
details?: string,
|
|
23
|
+
suggestions?: string[],
|
|
24
|
+
): CallToolResult;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export interface ResponseFormatterContract {
|
|
22
|
-
|
|
28
|
+
runWithMinifyOverride<T>(minifyOverride: boolean | undefined, fn: () => T): T;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface ToolRegistryCacheHelpers {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
generateKey?: (...segments: unknown[]) => string;
|
|
33
|
+
invalidate?: (key: string) => void | Promise<void>;
|
|
34
|
+
clear?: () => void | Promise<void>;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
export interface DefaultArgumentResolverContext {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
name: string;
|
|
39
|
+
accessToken: string;
|
|
40
|
+
rawArguments: Record<string, unknown>;
|
|
35
41
|
}
|
|
36
42
|
export class DefaultArgumentResolutionError extends Error {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
constructor(public readonly result: CallToolResult) {
|
|
44
|
+
super("Default argument resolution failed");
|
|
45
|
+
this.name = "DefaultArgumentResolutionError";
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export type DefaultArgumentResolver<TInput extends Record<string, unknown>> = (
|
|
44
|
-
|
|
50
|
+
context: DefaultArgumentResolverContext,
|
|
45
51
|
) => Partial<TInput> | Promise<Partial<TInput> | undefined> | undefined;
|
|
46
52
|
|
|
47
53
|
export interface ToolSecurityOptions {
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
namespace?: string;
|
|
55
|
+
operation?: string;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export interface ToolMetadataOptions {
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
inputJsonSchema?: Record<string, unknown>;
|
|
60
|
+
annotations?: MCPToolAnnotations;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/**
|
|
@@ -59,477 +65,505 @@ export interface ToolMetadataOptions {
|
|
|
59
65
|
* Follows MCP spec: notifications/progress
|
|
60
66
|
*/
|
|
61
67
|
export type ProgressCallback = (params: {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
progress: number;
|
|
69
|
+
total?: number;
|
|
70
|
+
message?: string;
|
|
65
71
|
}) => Promise<void>;
|
|
66
72
|
|
|
67
73
|
export interface ToolExecutionContext {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
accessToken: string;
|
|
75
|
+
name: string;
|
|
76
|
+
operation: string;
|
|
77
|
+
rawArguments: Record<string, unknown>;
|
|
78
|
+
cache?: ToolRegistryCacheHelpers;
|
|
79
|
+
/**
|
|
80
|
+
* Optional progress callback for emitting MCP progress notifications.
|
|
81
|
+
* Available when the client provides a progressToken in the request.
|
|
82
|
+
*/
|
|
83
|
+
sendProgress?: ProgressCallback;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
export interface ToolExecutionPayload<TInput extends Record<string, unknown>> {
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
input: TInput;
|
|
88
|
+
context: ToolExecutionContext;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
export type ToolHandler<TInput extends Record<string, unknown>> = (
|
|
86
|
-
|
|
92
|
+
payload: ToolExecutionPayload<TInput>,
|
|
87
93
|
) => Promise<CallToolResult>;
|
|
88
94
|
|
|
89
95
|
export interface ToolDefinition<
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
TInput extends Record<string, unknown> = Record<string, unknown>,
|
|
97
|
+
TOutput extends Record<string, unknown> = Record<string, unknown>,
|
|
92
98
|
> {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
name: string;
|
|
100
|
+
description: string;
|
|
101
|
+
inputSchema: z.ZodSchema<TInput>;
|
|
102
|
+
outputSchema?: z.ZodSchema<TOutput>;
|
|
103
|
+
handler: ToolHandler<TInput>;
|
|
104
|
+
security?: ToolSecurityOptions;
|
|
105
|
+
metadata?: ToolMetadataOptions;
|
|
106
|
+
defaultArgumentResolver?: DefaultArgumentResolver<TInput>;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
interface RegisteredTool<
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
TInput extends Record<string, unknown>,
|
|
111
|
+
TOutput extends Record<string, unknown>,
|
|
106
112
|
> extends ToolDefinition<TInput, TOutput> {
|
|
107
|
-
|
|
113
|
+
readonly security: Required<ToolSecurityOptions>;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
export interface ToolExecutionOptions {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
name: string;
|
|
118
|
+
accessToken: string;
|
|
119
|
+
arguments?: Record<string, unknown>;
|
|
120
|
+
minifyOverride?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Optional progress callback for emitting MCP progress notifications.
|
|
123
|
+
* Should be provided when the request includes a progressToken.
|
|
124
|
+
*/
|
|
125
|
+
sendProgress?: ProgressCallback;
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
export interface ToolRegistryDependencies {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
withSecurityWrapper: SecurityWrapperFactory;
|
|
130
|
+
errorHandler: ErrorHandlerContract;
|
|
131
|
+
responseFormatter: ResponseFormatterContract;
|
|
132
|
+
cacheHelpers?: ToolRegistryCacheHelpers;
|
|
133
|
+
validateAccessToken?: (token: string) => Promise<void> | void;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
const MINIFY_HINT_KEYS = [
|
|
136
|
+
const MINIFY_HINT_KEYS = ["minify", "_minify", "__minify"] as const;
|
|
131
137
|
|
|
132
138
|
export class ToolRegistry {
|
|
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
|
-
|
|
139
|
+
private readonly tools = new Map<
|
|
140
|
+
string,
|
|
141
|
+
RegisteredTool<Record<string, unknown>, Record<string, unknown>>
|
|
142
|
+
>();
|
|
143
|
+
private readonly outputValidators = new Map<
|
|
144
|
+
string,
|
|
145
|
+
z.ZodSchema<Record<string, unknown>>
|
|
146
|
+
>();
|
|
147
|
+
|
|
148
|
+
constructor(private readonly deps: ToolRegistryDependencies) {}
|
|
149
|
+
|
|
150
|
+
register<
|
|
151
|
+
TInput extends Record<string, unknown>,
|
|
152
|
+
TOutput extends Record<string, unknown>,
|
|
153
|
+
>(definition: ToolDefinition<TInput, TOutput>): void {
|
|
154
|
+
this.assertValidDefinition(definition);
|
|
155
|
+
|
|
156
|
+
if (this.tools.has(definition.name)) {
|
|
157
|
+
throw new Error(`Tool '${definition.name}' is already registered`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const resolved: RegisteredTool<TInput, TOutput> = {
|
|
161
|
+
...definition,
|
|
162
|
+
security: {
|
|
163
|
+
namespace: definition.security?.namespace ?? "ynab",
|
|
164
|
+
operation: definition.security?.operation ?? definition.name,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Type assertion is safe here because TInput/TOutput extend Record<string, unknown>
|
|
169
|
+
// and RegisteredTool is covariant in its type parameters for storage purposes
|
|
170
|
+
const registeredTool = resolved as RegisteredTool<
|
|
171
|
+
Record<string, unknown>,
|
|
172
|
+
Record<string, unknown>
|
|
173
|
+
>;
|
|
174
|
+
this.tools.set(definition.name, registeredTool);
|
|
175
|
+
|
|
176
|
+
// Cache output validator if present
|
|
177
|
+
if (definition.outputSchema) {
|
|
178
|
+
this.outputValidators.set(
|
|
179
|
+
definition.name,
|
|
180
|
+
definition.outputSchema as z.ZodSchema<Record<string, unknown>>,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
listTools(): Tool[] {
|
|
186
|
+
return Array.from(this.tools.values()).map((tool) => {
|
|
187
|
+
const inputSchema =
|
|
188
|
+
(tool.metadata?.inputJsonSchema as Tool["inputSchema"] | undefined) ??
|
|
189
|
+
(this.generateJsonSchema(tool.inputSchema) as Tool["inputSchema"]);
|
|
190
|
+
const result: Tool = {
|
|
191
|
+
name: tool.name,
|
|
192
|
+
description: tool.description,
|
|
193
|
+
inputSchema,
|
|
194
|
+
};
|
|
195
|
+
if (tool.outputSchema) {
|
|
196
|
+
const outputSchema = this.generateJsonSchema(
|
|
197
|
+
tool.outputSchema,
|
|
198
|
+
"output",
|
|
199
|
+
) as Tool["outputSchema"];
|
|
200
|
+
result.outputSchema = outputSchema;
|
|
201
|
+
}
|
|
202
|
+
if (tool.metadata?.annotations) {
|
|
203
|
+
result.annotations = tool.metadata.annotations;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
hasTool(name: string): boolean {
|
|
210
|
+
return this.tools.has(name);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getToolDefinitions(): ToolDefinition[] {
|
|
214
|
+
return Array.from(this.tools.values()).map((tool) => {
|
|
215
|
+
const definition: ToolDefinition = {
|
|
216
|
+
name: tool.name,
|
|
217
|
+
description: tool.description,
|
|
218
|
+
inputSchema: tool.inputSchema,
|
|
219
|
+
handler: tool.handler,
|
|
220
|
+
security: tool.security,
|
|
221
|
+
};
|
|
222
|
+
if (tool.outputSchema) {
|
|
223
|
+
definition.outputSchema = tool.outputSchema;
|
|
224
|
+
}
|
|
225
|
+
if (tool.metadata) {
|
|
226
|
+
definition.metadata = tool.metadata;
|
|
227
|
+
}
|
|
228
|
+
if (tool.defaultArgumentResolver) {
|
|
229
|
+
definition.defaultArgumentResolver = tool.defaultArgumentResolver;
|
|
230
|
+
}
|
|
231
|
+
return definition;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async executeTool(options: ToolExecutionOptions): Promise<CallToolResult> {
|
|
236
|
+
const tool = this.tools.get(options.name);
|
|
237
|
+
if (!tool) {
|
|
238
|
+
return this.deps.errorHandler.createValidationError(
|
|
239
|
+
`Unknown tool: ${options.name}`,
|
|
240
|
+
"The requested tool is not registered with the server",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this.deps.validateAccessToken) {
|
|
245
|
+
try {
|
|
246
|
+
await this.deps.validateAccessToken(options.accessToken);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (this.isCallToolResult(error)) {
|
|
249
|
+
return error;
|
|
250
|
+
}
|
|
251
|
+
return this.deps.errorHandler.handleError(
|
|
252
|
+
error,
|
|
253
|
+
`authenticating ${tool.name}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let defaults: Partial<Record<string, unknown>> | undefined;
|
|
259
|
+
|
|
260
|
+
if (tool.defaultArgumentResolver) {
|
|
261
|
+
try {
|
|
262
|
+
defaults = await tool.defaultArgumentResolver({
|
|
263
|
+
name: tool.name,
|
|
264
|
+
accessToken: options.accessToken,
|
|
265
|
+
rawArguments: options.arguments ?? {},
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof DefaultArgumentResolutionError) {
|
|
269
|
+
return error.result;
|
|
270
|
+
}
|
|
271
|
+
if (this.isCallToolResult(error)) {
|
|
272
|
+
return error;
|
|
273
|
+
}
|
|
274
|
+
return this.deps.errorHandler.createValidationError(
|
|
275
|
+
"Invalid parameters",
|
|
276
|
+
error instanceof Error
|
|
277
|
+
? error.message
|
|
278
|
+
: "Unknown error during default argument resolution",
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const rawArguments: Record<string, unknown> = {
|
|
284
|
+
...(defaults ?? {}),
|
|
285
|
+
...(options.arguments ?? {}),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const minifyOverride = this.extractMinifyOverride(options, rawArguments);
|
|
289
|
+
|
|
290
|
+
const run = async (): Promise<CallToolResult> => {
|
|
291
|
+
try {
|
|
292
|
+
const secured = this.deps.withSecurityWrapper(
|
|
293
|
+
tool.security.namespace,
|
|
294
|
+
tool.security.operation,
|
|
295
|
+
tool.inputSchema,
|
|
296
|
+
)(options.accessToken)(rawArguments);
|
|
297
|
+
|
|
298
|
+
return await secured(async (validated) => {
|
|
299
|
+
try {
|
|
300
|
+
const context: ToolExecutionContext = {
|
|
301
|
+
accessToken: options.accessToken,
|
|
302
|
+
name: tool.name,
|
|
303
|
+
operation: tool.security.operation,
|
|
304
|
+
rawArguments,
|
|
305
|
+
};
|
|
306
|
+
if (this.deps.cacheHelpers) {
|
|
307
|
+
context.cache = this.deps.cacheHelpers;
|
|
308
|
+
}
|
|
309
|
+
if (options.sendProgress) {
|
|
310
|
+
context.sendProgress = options.sendProgress;
|
|
311
|
+
}
|
|
312
|
+
const handlerResult = await tool.handler({
|
|
313
|
+
input: validated,
|
|
314
|
+
context,
|
|
315
|
+
});
|
|
316
|
+
// Validate output against schema if present
|
|
317
|
+
// Skip validation if handler returned an error
|
|
318
|
+
if (handlerResult.isError) {
|
|
319
|
+
return handlerResult;
|
|
320
|
+
}
|
|
321
|
+
return this.validateOutput(tool.name, handlerResult);
|
|
322
|
+
} catch (handlerError) {
|
|
323
|
+
return this.deps.errorHandler.handleError(
|
|
324
|
+
handlerError,
|
|
325
|
+
`executing ${tool.name} - ${tool.security.operation}`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
} catch (securityError) {
|
|
330
|
+
return this.normalizeSecurityError(securityError, tool);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
return await this.deps.responseFormatter.runWithMinifyOverride(
|
|
336
|
+
minifyOverride,
|
|
337
|
+
run,
|
|
338
|
+
);
|
|
339
|
+
} catch (formatterError) {
|
|
340
|
+
return this.deps.errorHandler.handleError(
|
|
341
|
+
formatterError,
|
|
342
|
+
`formatting response for ${tool.name}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private isCallToolResult(value: unknown): value is CallToolResult {
|
|
348
|
+
return (
|
|
349
|
+
typeof value === "object" &&
|
|
350
|
+
value !== null &&
|
|
351
|
+
"content" in (value as Record<string, unknown>) &&
|
|
352
|
+
Array.isArray((value as { content?: unknown }).content)
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private normalizeSecurityError(
|
|
357
|
+
error: unknown,
|
|
358
|
+
tool: RegisteredTool<Record<string, unknown>, Record<string, unknown>>,
|
|
359
|
+
): CallToolResult {
|
|
360
|
+
if (error instanceof z.ZodError) {
|
|
361
|
+
const validationError = fromZodError(error);
|
|
362
|
+
return this.deps.errorHandler.createValidationError(
|
|
363
|
+
`Invalid parameters for ${tool.name}`,
|
|
364
|
+
validationError.message,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (error instanceof Error && error.message.includes("Validation failed")) {
|
|
369
|
+
return this.deps.errorHandler.createValidationError(
|
|
370
|
+
`Invalid parameters for ${tool.name}`,
|
|
371
|
+
error.message,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return this.deps.errorHandler.handleError(error, `executing ${tool.name}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private extractMinifyOverride(
|
|
379
|
+
options: ToolExecutionOptions,
|
|
380
|
+
args: Record<string, unknown>,
|
|
381
|
+
): boolean | undefined {
|
|
382
|
+
if (typeof options.minifyOverride === "boolean") {
|
|
383
|
+
return options.minifyOverride;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const key of MINIFY_HINT_KEYS) {
|
|
387
|
+
const value = args[key];
|
|
388
|
+
if (typeof value === "boolean") {
|
|
389
|
+
// Remove the minify hint key from args
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
391
|
+
delete args[key];
|
|
392
|
+
return value;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Regex pattern for MCP-compliant tool names.
|
|
401
|
+
* Tool names SHOULD be 1-128 chars, case-sensitive, only [a-zA-Z0-9_.-]
|
|
402
|
+
* @see https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/
|
|
403
|
+
*/
|
|
404
|
+
private static readonly MCP_TOOL_NAME_REGEX = /^[a-zA-Z0-9_.-]{1,128}$/;
|
|
405
|
+
|
|
406
|
+
private assertValidDefinition<
|
|
407
|
+
TInput extends Record<string, unknown>,
|
|
408
|
+
TOutput extends Record<string, unknown>,
|
|
409
|
+
>(definition: ToolDefinition<TInput, TOutput>): void {
|
|
410
|
+
if (!definition || typeof definition !== "object") {
|
|
411
|
+
throw new Error("Tool definition must be an object");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!definition.name || typeof definition.name !== "string") {
|
|
415
|
+
throw new Error("Tool definition requires a non-empty name");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Validate tool name follows MCP specification guidelines
|
|
419
|
+
if (!ToolRegistry.MCP_TOOL_NAME_REGEX.test(definition.name)) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
`Tool name '${definition.name}' violates MCP guidelines: must be 1-128 chars using only [a-zA-Z0-9_.-]`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!definition.description || typeof definition.description !== "string") {
|
|
426
|
+
throw new Error(`Tool '${definition.name}' requires a description`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (
|
|
430
|
+
!definition.inputSchema ||
|
|
431
|
+
typeof definition.inputSchema.parse !== "function"
|
|
432
|
+
) {
|
|
433
|
+
throw new Error(`Tool '${definition.name}' requires a valid Zod schema`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
definition.outputSchema &&
|
|
438
|
+
typeof definition.outputSchema.parse !== "function"
|
|
439
|
+
) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`Tool '${definition.name}' outputSchema must be a valid Zod schema when provided`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (typeof definition.handler !== "function") {
|
|
446
|
+
throw new Error(`Tool '${definition.name}' requires a handler function`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (
|
|
450
|
+
definition.defaultArgumentResolver &&
|
|
451
|
+
typeof definition.defaultArgumentResolver !== "function"
|
|
452
|
+
) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Tool '${definition.name}' defaultArgumentResolver must be a function when provided`,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private generateJsonSchema(
|
|
460
|
+
schema: z.ZodTypeAny,
|
|
461
|
+
ioMode: "input" | "output" = "input",
|
|
462
|
+
): Record<string, unknown> {
|
|
463
|
+
try {
|
|
464
|
+
return toJSONSchema(schema, { target: "draft-2020-12", io: ioMode });
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.warn(`Failed to generate JSON schema for tool: ${error}`);
|
|
467
|
+
return { type: "object", additionalProperties: true };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Validates handler output against the tool's output schema if present
|
|
473
|
+
*/
|
|
474
|
+
private validateOutput(
|
|
475
|
+
toolName: string,
|
|
476
|
+
output: CallToolResult,
|
|
477
|
+
): CallToolResult {
|
|
478
|
+
const validator = this.outputValidators.get(toolName);
|
|
479
|
+
if (!validator) {
|
|
480
|
+
// No output schema defined, skip validation
|
|
481
|
+
return output;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Extract the actual data from the CallToolResult
|
|
485
|
+
// CallToolResult is { content: Array<{ type: string, text: string, ... }> }
|
|
486
|
+
// We need to parse the text content and validate it
|
|
487
|
+
if (!output.content || output.content.length === 0) {
|
|
488
|
+
return this.deps.errorHandler.createValidationError(
|
|
489
|
+
`Output validation failed for ${toolName}`,
|
|
490
|
+
"Handler returned empty content",
|
|
491
|
+
["Ensure the handler returns valid content in the response"],
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Validate all content items (not just the first one)
|
|
496
|
+
const invalidItems: { index: number; reason: string }[] = [];
|
|
497
|
+
|
|
498
|
+
for (let i = 0; i < output.content.length; i++) {
|
|
499
|
+
const item = output.content[i];
|
|
500
|
+
if (!item) {
|
|
501
|
+
invalidItems.push({ index: i, reason: "item is null or undefined" });
|
|
502
|
+
} else if (item.type !== "text") {
|
|
503
|
+
invalidItems.push({
|
|
504
|
+
index: i,
|
|
505
|
+
reason: `type is "${item.type}" instead of "text"`,
|
|
506
|
+
});
|
|
507
|
+
} else if (typeof item.text !== "string") {
|
|
508
|
+
invalidItems.push({
|
|
509
|
+
index: i,
|
|
510
|
+
reason: `text property is ${typeof item.text} instead of string`,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (invalidItems.length > 0) {
|
|
516
|
+
const invalidItemsDetails = invalidItems
|
|
517
|
+
.map((inv) => ` - Item ${inv.index}: ${inv.reason}`)
|
|
518
|
+
.join("\n");
|
|
519
|
+
|
|
520
|
+
return this.deps.errorHandler.createValidationError(
|
|
521
|
+
`Output validation failed for ${toolName}`,
|
|
522
|
+
`Handler returned invalid content items (${invalidItems.length} of ${output.content.length} failed):\n${invalidItemsDetails}`,
|
|
523
|
+
['Ensure all content items have type="text" and a valid text property'],
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const firstContent = output.content[0];
|
|
528
|
+
if (!firstContent) {
|
|
529
|
+
return this.deps.errorHandler.createValidationError(
|
|
530
|
+
`Output validation failed for ${toolName}`,
|
|
531
|
+
"Handler returned empty content",
|
|
532
|
+
["Ensure the handler returns valid content in the response"],
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
// TypeScript: After validation above, we know firstContent.type === 'text'
|
|
536
|
+
if (firstContent.type !== "text") {
|
|
537
|
+
throw new Error("Unexpected: firstContent is not text after validation");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
let parsedOutput: unknown;
|
|
541
|
+
try {
|
|
542
|
+
parsedOutput = JSON.parse(firstContent.text);
|
|
543
|
+
} catch (parseError) {
|
|
544
|
+
return this.deps.errorHandler.createValidationError(
|
|
545
|
+
`Output validation failed for ${toolName}`,
|
|
546
|
+
`Invalid JSON in handler output: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
547
|
+
["Ensure the handler returns valid JSON"],
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Validate against schema
|
|
552
|
+
const result = validator.safeParse(parsedOutput);
|
|
553
|
+
if (!result.success) {
|
|
554
|
+
const validationError = fromZodError(result.error);
|
|
555
|
+
const validationErrors = validationError.message;
|
|
556
|
+
return this.deps.errorHandler.createValidationError(
|
|
557
|
+
`Output validation failed for ${toolName}`,
|
|
558
|
+
`Handler output does not match declared output schema: ${validationErrors}`,
|
|
559
|
+
[
|
|
560
|
+
"Check that the handler returns data matching the output schema",
|
|
561
|
+
"Review the tool definition output schema",
|
|
562
|
+
],
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Validation passed, return original output
|
|
567
|
+
return output;
|
|
568
|
+
}
|
|
535
569
|
}
|