From a87b87734563ea5ffd61efcbed2ca8d0afa3da27 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 11:58:41 +0000 Subject: [PATCH 1/8] chore(deps): update vite-plugin-dts to ^5.1.2 Clears the sprintf-js / minimatch advisories reported through vite-plugin-dts 4 (@microsoft/api-extractor, @vue/language-core). api-extractor 7.60.1 is the latest release and still pins minimatch 10.2.3 (GHSA-g3hv-75wg-m249), so an override moves only that dev-only dependency to minimatch ^11.0.1. The built dist (declarations and bundle) is identical to the previous build. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGSYMuTcM7fquxzRook --- package-lock.json | 553 +++++++++++++++++++++++++++------------------- package.json | 7 +- 2 files changed, 333 insertions(+), 227 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bd6e1c..e411507 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "tsx": "^4.19.0", "typescript": "^5.9.2", "vite": "^7.2.4", - "vite-plugin-dts": "^4.5.4", + "vite-plugin-dts": "^5.1.2", "vitest": "^5.0.3" }, "engines": { @@ -675,6 +675,37 @@ "hono": "^4 || ^5.0.0-0" } }, + "node_modules/@isaacs/nonrecursive-glob-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz", + "integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "peer": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -709,6 +740,8 @@ "integrity": "sha512-1FJ/Z30xLL3pWn6oYSJCjGsnlEWkWivwmVeCB7YrRPe+47J7H54NVR+m/1PIL1RYsh/mZsxm/FwirreD72G76g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@microsoft/api-extractor-model": "7.33.16", "@microsoft/tsdoc": "~0.17.0", @@ -737,6 +770,8 @@ "integrity": "sha512-MO61eHRI3DDieE9pxNo5xc096NGUQAIKzEPW6IhNNGU9K4EIhRSYiXfroDZX4joNkrWBMzAMp3WhbPGbHG5zHQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@microsoft/tsdoc": "~0.17.0", "@microsoft/tsdoc-config": "~0.18.2", @@ -746,51 +781,14 @@ "node": ">=20.9.0" } }, - "node_modules/@microsoft/api-extractor/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", - "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@microsoft/api-extractor/node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -804,7 +802,9 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.17.1.tgz", "integrity": "sha512-VHyYyyoH63jFoO3shpw9Zfijk5v/9p/VrClW35z0/q6ooK7sSmxpzOkS4iZPxs3vO4XtBW4H38HRAKkAMIK7DA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@microsoft/tsdoc-config": { "version": "0.18.3", @@ -812,6 +812,8 @@ "integrity": "sha512-0I0rGXo1eJziw3EEErkDS9p/0RzBcyOiV+zbSDqOZTeURmhtut+WIvpih0LM5g+ZpZ9SyFxDfkhAcl/r8e2zGw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@microsoft/tsdoc": "0.17.1", "ajv": "~8.18.0", @@ -886,9 +888,9 @@ "license": "MIT" }, "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.5.1.tgz", + "integrity": "sha512-P9ZvnfouRu4MSC+ilERaw6FmZs8zqKhpHxEs4aGDGWQAMzQGXnhvCSvoNwokfNvdt7t3Y5Y4p6gStQgezeIqug==", "dev": true, "license": "MIT", "dependencies": { @@ -1271,6 +1273,8 @@ "integrity": "sha512-76QGqWRVMtd/KZqhfSOMBgDvxIUsYPhUBhLSorGdn/wwkuESoJ2/cUI3KmfMKrZrE72o8Lau4P8kCBH/omI17Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ajv": "~8.20.0", "ajv-draft-04": "~1.0.0", @@ -1299,6 +1303,8 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1316,6 +1322,8 @@ "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "@types/node": "*" }, @@ -1331,6 +1339,8 @@ "integrity": "sha512-SHZdzc9b2ptUb5uvRWALtAceD/QKZ4UAnZjcaKviTMPmHBnW14gs7XDocn6sDv2EqgnxxFPFkpV7/I3cUijCbw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jju": "~1.4.0", "resolve": "~1.22.1" @@ -1345,6 +1355,8 @@ "integrity": "sha512-VJAq8ZVgnEtXOC+FnBL3YdP5YNBibT9JvRvEuXsqi0ENZ+Ha0uR9CaK2c+4y8hdk/n6Y2SJ6+AZYDfSQmqupWg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@rushstack/node-core-library": "5.25.2", "@rushstack/problem-matcher": "0.2.1", @@ -1368,6 +1380,8 @@ "integrity": "sha512-acP68tGx/DJEIQgTqj/xStBRaghN7axV+uAjNasDjVXgHgHaLQ1f16/bbXtw6mNLjURhTORD4fdfbOZM7kyWcw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@rushstack/terminal": "0.24.8", "@types/argparse": "1.0.38", @@ -1383,7 +1397,9 @@ "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@types/chai": { "version": "5.2.3", @@ -1531,95 +1547,57 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", - "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.30.tgz", + "integrity": "sha512-73fjwEV8wat6aOZ6U/y/hqtE30ocrIWECg67b0txGczOMNw+MffLmvJ5fnFfN9drVUEYZzLdaNo6VAy6G6avMw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.27" + "@volar/source-map": "2.4.30" } }, - "node_modules/@volar/source-map": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", - "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.27", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", - "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "node_modules/@volar/language-service": { + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.30.tgz", + "integrity": "sha512-w/xDsASiqC71ggCOm7sTDXDNk/l72XEptodeRC68tgZ0e5QM4hM3zAqBGC7mBctvhrpYwKfMG+t9xcUo2hEFtA==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.27", - "path-browserify": "^1.0.1", + "@volar/language-core": "2.4.30", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "<7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", - "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.26", - "entities": "^7.0.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/@volar/source-map": { + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.30.tgz", + "integrity": "sha512-zym1ob/KwDDyN+0jGVtxwbxJSRRnUvv5HspCtz3yEfVVdCG4/dzxJktMGPAgemqRuVs81O0o/y0q3qOUErR8kg==", "dev": true, "license": "MIT" }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", - "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.26", - "@vue/shared": "3.5.26" - } - }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/language-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "node_modules/@volar/typescript": { + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.30.tgz", + "integrity": "sha512-/DgA5tgYFPfrLFuSjIMXMGHk1LQv2+P8eny6+BhGNzFJELWIW7UZlc56ZLIRFhLsPpMfIFxM8/HvOPBggrjTZg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "~2.4.11", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^0.4.9", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" + "@volar/language-core": "2.4.30", + "@volar/language-service": "2.4.30", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" }, "peerDependencies": { - "typescript": "*" + "typescript": "<7" }, "peerDependenciesMeta": { "typescript": { @@ -1627,13 +1605,6 @@ } } }, - "node_modules/@vue/shared": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", - "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", - "dev": true, - "license": "MIT" - }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", @@ -1641,9 +1612,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", "bin": { @@ -1675,6 +1646,8 @@ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "ajv": "^8.5.0" }, @@ -1701,19 +1674,14 @@ } } }, - "node_modules/alien-signals": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -1760,20 +1728,30 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/chai": { @@ -1794,9 +1772,9 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.2.tgz", + "integrity": "sha512-ZUAKhKlkCOrIGKc8cj7zek9s9z1mveHif/25nUshI2BzCRzF7KH7KHa+Lnh2ubq+e1tIlaFuP763mzW4qVoehg==", "dev": true, "license": "MIT" }, @@ -1815,13 +1793,6 @@ "node": ">= 8" } }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1855,29 +1826,20 @@ "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } }, - "node_modules/entities": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", - "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.4" } @@ -1975,9 +1937,9 @@ } }, "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.2.0.tgz", + "integrity": "sha512-h1pQPR9XoeSMdntRNCjyTznaume4UpfXrULBnlkZ2CUM/G5eSa+pBByGldO5uR8dxbPC+OuJXsPPNf+JZ81ADg==", "dev": true, "license": "MIT" }, @@ -2178,6 +2140,8 @@ "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2208,6 +2172,8 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2225,12 +2191,21 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/has-flag": { "version": "4.0.0", @@ -2238,6 +2213,8 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -2248,6 +2225,8 @@ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -2255,16 +2234,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hono": { "version": "4.13.16", "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.16.tgz", @@ -2280,6 +2249,8 @@ "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -2299,6 +2270,8 @@ "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "hasown": "^2.0.4" }, @@ -2321,7 +2294,9 @@ "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/jose": { "version": "6.2.15", @@ -2371,6 +2346,8 @@ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2436,9 +2413,9 @@ "license": "MIT" }, "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2476,32 +2453,35 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.4.tgz", + "integrity": "sha512-/UJ1dEoVQTjPNDh6SGlUo+ueLMmoHTas4q+8yaAR/5t6nLAIiBDJtoYUh20IYleix7ehiQkgczb84PE3uzHkxg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", + "optional": true, + "peer": true, "dependencies": { - "brace-expansion": "^2.0.2" + "@isaacs/nonrecursive-glob-match": "^1.0.3", + "brace-expansion": "^5.0.12" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", - "ufo": "^1.6.1" + "ufo": "^1.6.3" } }, "node_modules/mlly/node_modules/confbox": { @@ -2539,13 +2519,6 @@ "dev": true, "license": "MIT" }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", @@ -2616,7 +2589,9 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/pathe": { "version": "2.0.3", @@ -2693,14 +2668,14 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz", + "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==", "dev": true, "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.3.1", + "exsolve": "^1.1.3", "pathe": "^2.0.3" } }, @@ -2796,6 +2771,8 @@ "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.17.0", @@ -2995,6 +2972,8 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3023,7 +3002,9 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true, + "peer": true }, "node_modules/std-env": { "version": "4.3.0", @@ -3038,6 +3019,8 @@ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.6.19" } @@ -3048,6 +3031,8 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3064,6 +3049,8 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.4" }, @@ -3171,9 +3158,9 @@ } }, "node_modules/ufo": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.2.tgz", - "integrity": "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "dev": true, "license": "MIT" }, @@ -3190,10 +3177,83 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 10.0.0" } }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-dts": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unplugin-dts/-/unplugin-dts-1.1.2.tgz", + "integrity": "sha512-RDqy90XUmFj6rz4U5YoTMdYdaCJe2ZnQZaWIjkWS4952Gv0zBTQyqWtlGgVMPYHa982dURvyoc+sUOFIcDFv+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.26", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "glob-to-regexp": "0.4.1", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "unplugin": "^2.3.2" + }, + "peerDependencies": { + "@microsoft/api-extractor": ">=7", + "@rspack/core": "^1", + "@vue/language-core": "^3.1.5", + "esbuild": "*", + "rolldown": "*", + "rollup": ">=3", + "typescript": ">=4", + "vite": ">=3", + "webpack": "^4 || ^5" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "@vue/language-core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/vite": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.7.tgz", @@ -3270,27 +3330,26 @@ } }, "node_modules/vite-plugin-dts": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", - "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-5.1.2.tgz", + "integrity": "sha512-1bXUz836sWq6ewEMQre3ZtNFVfZk/92DppgCCKP8tWx/NWwV2wHuXlRHb2TztHjbikPxvyAGLGu7hVh94Plwww==", "dev": true, "license": "MIT", "dependencies": { - "@microsoft/api-extractor": "^7.50.1", - "@rollup/pluginutils": "^5.1.4", - "@volar/typescript": "^2.4.11", - "@vue/language-core": "2.2.0", - "compare-versions": "^6.1.1", - "debug": "^4.4.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", - "magic-string": "^0.30.17" + "unplugin-dts": "1.1.2" }, "peerDependencies": { - "typescript": "*", - "vite": "*" + "@microsoft/api-extractor": ">=7", + "rollup": ">=3", + "vite": ">=3" }, "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "rollup": { + "optional": true + }, "vite": { "optional": true } @@ -3389,10 +3448,52 @@ "@jridgewell/sourcemap-codec": "^1.6.0" } }, + "node_modules/vscode-jsonrpc": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-10.0.0.tgz", + "integrity": "sha512-USSfxg0vDIpTQqlQBGxF3mBARyoS8cU7LHLpbYNh6dOJt32dlskxPWB0Irrh5TllTmyf7oNLAvqDqzklbwwnpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.5.tgz", + "integrity": "sha512-FW4fBMzZ99N8xeob4py0l050wgCoRgTpEq5Xcsb9lM1bc29UKGR/wSV+4knaqFxssFHajy7OzNiKP/m3U1ZEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "10.0.0", + "vscode-languageserver-types": "3.18.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.16.tgz", + "integrity": "sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.5.tgz", + "integrity": "sha512-kPd6+JQNz3HMJvjKlqUxERSlZHAntl/aGpsk7ImwIh0ewDKDcjgAEyVSYciPiOJD3ftbK9IrfiqUeTZLSbCizw==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 7f8a88e..f6004be 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "tsx": "^4.19.0", "typescript": "^5.9.2", "vite": "^7.2.4", - "vite-plugin-dts": "^4.5.4", + "vite-plugin-dts": "^5.1.2", "vitest": "^5.0.3" }, "engines": { @@ -79,5 +79,10 @@ }, "bugs": { "url": "https://github.com/code-rabi/toolception/issues" + }, + "overrides": { + "@microsoft/api-extractor": { + "minimatch": "^11.0.1" + } } } From 5b43dfa9b99db285a5d3a96f096d998411a7e26d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 11:59:06 +0000 Subject: [PATCH 2/8] ci: run CI and release on Node 26 vitest 5 declares node ^22.12 || ^24 || >=26, so the pinned Node 25.3.0 is outside its supported range. Use Node 26 in ci.yml and release.yml; `engines` stays >=25.3.0 so consumers are unaffected. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGSYMuTcM7fquxzRook --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- CLAUDE.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 646ab9d..3a21036 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "25.3.0" + node-version: "26" cache: "npm" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ca520a..26c3e00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "25.3.0" + node-version: "26" cache: "npm" registry-url: "https://registry.npmjs.org" diff --git a/CLAUDE.md b/CLAUDE.md index 120b76c..ed6fa69 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,4 +63,4 @@ Tests use Vitest with in-memory mocks. Key patterns: - Vite for bundling (`vite.config.ts`) - vite-plugin-dts for TypeScript declarations - ESM-only output (`"type": "module"`) -- Node.js >= 25.3.0 required +- Node.js >= 25.3.0 required (`engines`). CI and the release workflow run on Node 26, because vitest 5 supports `^22.12 || ^24 || >=26` and not Node 25 From 842cce39ed09616e838b3b85a828333d9583c5be Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 12:03:57 +0000 Subject: [PATCH 3/8] fix: advertise and validate JSON Schema tool inputs with fromJsonSchema JSON Schema inputSchema values registered as an open object, so clients saw no parameters and arguments were not validated. They now go through the SDK v2 fromJsonSchema(), with one validator per DynamicToolManager so a client's schemas (and $id values) never cross to another client. Schemas that do not compile, or are not a Zod 4 shape or a JSON Schema object, fail the toolset's enable with E_VALIDATION. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTprrczjscL9XyWxYz --- CHANGELOG.md | 4 + README.md | 2 +- src/core/AGENTS.md | 5 +- src/core/DynamicToolManager.ts | 72 ++++++++++--- src/types/AGENTS.md | 2 +- src/types/index.ts | 6 +- tests/dynamicToolManager.test.ts | 150 +++++++++++++++++++++++++- tests/e2e/jsonSchemaTools.e2e.test.ts | 23 ++++ 8 files changed, 245 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8dfb25..ffd66d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Releases before 0.7.0 are described in [GitHub Releases](https://github.com/code - `createServer` factories must return a v2 `McpServer`; the removed `server.tool()`, `server.prompt()` and `server.resource()` are replaced by `registerTool`, `registerPrompt` and `registerResource`. - Calling a tool the server no longer lists (for example a disabled toolset's tool) is now answered with a JSON-RPC error ("Tool ... not found") instead of a result with `isError: true`. This is SDK v2 behaviour. - `FastifyTransport` and `PermissionAwareFastifyTransport` use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. Session handling, the per-client `mcp-client-id` model and the HTTP endpoints are unchanged: each client still gets its own server, transport and enabled/disabled toolset state. +- A tool whose `inputSchema` is a plain JSON Schema object (`type: "object"`) is now registered with the SDK's `fromJsonSchema()`: clients see its real properties and arguments are validated against it before your handler runs. Until now such a tool registered as an open object, so its parameters were not advertised and any arguments reached the handler. Invalid arguments now get an error result and the handler is not called. + - **Migration:** if a handler relied on receiving arguments its schema forbids (a missing `required` property, a wrong type, or extra properties under `additionalProperties: false`), loosen the schema or fix the caller. + - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool, instead of failing at `tools/list`. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (no root `type: "object"`, or a shape that mixes Zod and non-Zod values), which used to register as an open object. + - Each client's `DynamicToolManager` compiles JSON Schemas with its own validator, so one client's schema never answers for another's, even when both declare the same `$id`. ### Fixed diff --git a/README.md b/README.md index 9013a5d..2b3c397 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ const catalog = { ### Step 4: Define a tool -`inputSchema` should be a Zod 4 raw shape (or a Zod 4 object schema); toolception wraps a raw shape with `z.object()` before it calls `McpServer.registerTool`. A Zod 3 schema fails to enable its toolset with an `E_VALIDATION` error. A plain JSON Schema object still works, but clients see an open object schema and arguments reach the handler unvalidated. +`inputSchema` should be a Zod 4 raw shape (or a Zod 4 object schema); toolception wraps a raw shape with `z.object()` before it calls `McpServer.registerTool`. A Zod 3 schema fails to enable its toolset with an `E_VALIDATION` error. A plain JSON Schema object (`type: "object"`) also works: toolception registers it with the SDK's `fromJsonSchema()`, so clients see its properties and arguments are validated against it before your handler runs. A JSON Schema that does not compile (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) fails the toolset's enable with an `E_VALIDATION` error that names the tool. ```ts import { z } from "zod"; diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index 5eb3de9..cbd82c1 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -18,8 +18,9 @@ Central orchestration layer. Manages toolset lifecycle, tool registration, and s 4. **Namespacing applied BEFORE collision check** — `mapAndValidate()` generates safe names first, then checks for conflicts. 5. **Toolset added to activeToolsets AFTER registration** — All tools must register successfully before toolset is marked active. A partial failure removes the tools already registered, so nothing is left callable and the toolset can be enabled again. 6. **`enableToolsets()` batches notifications** — Calls `enableToolset(name, skipNotification=true)` per toolset, sends one `tools/list_changed` notification at the end. The SDK also sends its own `tools/list_changed` for every `registerTool()` and `remove()` call on a connected server, so a disable of N tools sends N+1. -7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; anything else is treated as plain JSON Schema and registers as `z.looseObject({})`, so clients see an open object schema, not the declared one, and arguments are not validated. +7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing schema registers as `z.looseObject({})`; anything else (no root `type: "object"`, a shape mixing Zod and non-Zod values, a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails instead of `tools/list`. 8. **Failed tool registration at startup makes the orchestrator not ready** — `enableToolsets()` reports failures in its result and never throws. A failure that carries a `code` (set when enabling threw, normally while registering the toolset's tools, e.g. a tool name collision) becomes `initError`, so `isReady()` is `false` and `ensureReady()` throws. Refusals made before any registration (invalid name, already enabled, exposure policy) carry no code and are ignored. Module loader errors are also ignored: `ModuleResolver` logs them and continues. +9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) lazily creates its own `AjvJsonSchemaValidator`. The SDK's shared default validator caches compiled schemas by `$id` and by object for the whole process, so one client's schema could answer for another's and the cache would grow with every client. Do not switch to `fromJsonSchema(schema)` without a validator argument. ## Enable Toolset Flow @@ -34,7 +35,7 @@ enableToolset(name) ## SDK registration -Tools are registered with `McpServer.registerTool` (`server.tool()` no longer exists in SDK v2). The SDK does not accept a plain JSON Schema object as `inputSchema`, so `toRegistrableInputSchema()` maps every tool schema to a Zod 4 schema first. Calling a tool that is not (or no longer) registered makes the SDK answer with a JSON-RPC error, not an `isError` result. +Tools are registered with `McpServer.registerTool` (`server.tool()` no longer exists in SDK v2). The SDK does not accept a plain JSON Schema object as `inputSchema`, so `toRegistrableInputSchema()` maps every tool schema to a Zod 4 schema, or wraps a JSON Schema with `fromJsonSchema()`, first. Calling a tool that is not (or no longer) registered makes the SDK answer with a JSON-RPC error, not an `isError` result. ## Anti-patterns diff --git a/src/core/DynamicToolManager.ts b/src/core/DynamicToolManager.ts index 665cfa8..144eaa8 100644 --- a/src/core/DynamicToolManager.ts +++ b/src/core/DynamicToolManager.ts @@ -1,4 +1,12 @@ -import type { McpServer, RegisteredTool } from "@modelcontextprotocol/server"; +import { + fromJsonSchema, + type JsonSchemaType, + type McpServer, + type RegisteredTool, + type StandardSchemaWithJSON, + type jsonSchemaValidator, +} from "@modelcontextprotocol/server"; +import { AjvJsonSchemaValidator } from "@modelcontextprotocol/server/validators/ajv"; import { z } from "zod"; import type { ExposurePolicy, @@ -22,6 +30,13 @@ export class DynamicToolManager { private readonly activeToolsets = new Set(); /** SDK handles of each toolset's registered tools, used to unregister them. */ private readonly toolHandles = new Map(); + /** + * Validator for JSON Schema tools, created on first use. It is deliberately + * per manager (so per client): the SDK's shared default validator caches + * compiled schemas by `$id` and by object for the life of the process, which + * would let one client's schema answer for another's and grow with every client. + */ + private jsonSchemaValidator?: jsonSchemaValidator; constructor(options: DynamicToolManagerOptions) { this.server = options.server; @@ -247,7 +262,11 @@ export class DynamicToolManager { const hasAnnotations = tool.annotations && Object.keys(tool.annotations).length > 0; - const inputSchema = toRegistrableInputSchema(tool.name, tool.inputSchema); + const inputSchema = toRegistrableInputSchema( + tool.name, + tool.inputSchema, + () => (this.jsonSchemaValidator ??= new AjvJsonSchemaValidator()) + ); const handle = this.server.registerTool( tool.name, @@ -389,7 +408,10 @@ export class DynamicToolManager { } /** The schema type handed to `registerTool`; arguments arrive as a plain record. */ -type InputSchema = z.ZodType>; +type InputSchema = StandardSchemaWithJSON< + Record, + Record +>; /** * @param value - Any value @@ -413,26 +435,34 @@ function isZod3Type(value: unknown): boolean { } /** - * Turns a tool's `inputSchema` into the Zod 4 object schema that - * `McpServer.registerTool` expects. + * Turns a tool's `inputSchema` into the schema that `McpServer.registerTool` + * expects. * * - A Zod 4 schema instance is used as is. * - A raw shape of Zod 4 schemas (an empty object counts as an empty shape) is * wrapped with toolception's own `z.object()`, so the SDK never wraps it with * a second copy of Zod. - * - Anything else is treated as plain JSON Schema and registers as an open - * object, so arguments still reach the handler. + * - A plain JSON Schema object (`type: "object"`) goes through the SDK's + * `fromJsonSchema()`: clients see its real properties and arguments are + * validated against it before the handler runs. + * - A missing schema registers as an open object, so arguments still reach the handler. * - * @param toolName - The tool name, used in the error message + * @param toolName - The tool name, used in error messages * @param schema - The tool's inputSchema + * @param getValidator - Supplies the JSON Schema validator; called only for JSON Schema tools * @returns A schema to pass to `registerTool` - * @throws ToolingError `E_VALIDATION` when the schema or a shape value is a Zod 3 schema + * @throws ToolingError `E_VALIDATION` when the schema or a shape value is a Zod 3 schema, + * when the schema is neither a Zod 4 shape nor a JSON Schema object, or when the JSON + * Schema cannot be compiled (bad keyword, unresolved `$ref`, unsupported dialect) */ function toRegistrableInputSchema( toolName: string, - schema: unknown + schema: unknown, + getValidator: () => jsonSchemaValidator ): InputSchema { - if (typeof schema !== "object" || schema === null) return z.looseObject({}); + if (typeof schema !== "object" || schema === null) { + return z.looseObject({}) as InputSchema; + } const values = isZod4Type(schema) || isZod3Type(schema) ? [schema] : Object.values(schema); @@ -448,5 +478,23 @@ function toRegistrableInputSchema( if (values.every(isZod4Type)) { return z.object(schema as Record) as InputSchema; } - return z.looseObject({}); + if ((schema as { type?: unknown }).type !== "object") { + throw new ToolingError( + `Tool '${toolName}' has an inputSchema that is neither a Zod 4 shape nor a JSON Schema object (type: "object").`, + "E_VALIDATION" + ); + } + try { + return fromJsonSchema>( + schema as JsonSchemaType, + getValidator() + ); + } catch (error) { + throw new ToolingError( + `Tool '${toolName}' has an invalid JSON Schema inputSchema: ${ + error instanceof Error ? error.message : String(error) + }`, + "E_VALIDATION" + ); + } } diff --git a/src/types/AGENTS.md b/src/types/AGENTS.md index 4c0a3c0..6f55f35 100644 --- a/src/types/AGENTS.md +++ b/src/types/AGENTS.md @@ -23,7 +23,7 @@ Also: `src/errors/ToolingError.ts` (18 LOC) — thin error class wrapping Toolin 2. **ToolingErrorCode values are exhaustive** — All error codes defined here; do not add codes elsewhere 3. **PermissionConfig requires source field** — Must specify `"headers"` or `"config"` 4. **SessionContextConfig.allowedKeys is security-critical** — Always whitelist allowed keys; omitting allows arbitrary config injection -5. **`McpToolDefinition.inputSchema` should be Zod 4** — A Zod 4 raw shape or Zod 4 object schema. A Zod 3 schema fails the toolset's enable with `E_VALIDATION`. `McpServer.registerTool` does not accept JSON Schema, so `DynamicToolManager` registers JSON Schema tools with an open object (`z.looseObject({})`: args unvalidated, properties not advertised) +5. **`McpToolDefinition.inputSchema` should be Zod 4** — A Zod 4 raw shape or Zod 4 object schema. A Zod 3 schema fails the toolset's enable with `E_VALIDATION`. A plain JSON Schema object (`type: "object"`) is registered through the SDK's `fromJsonSchema()`, so it is advertised and its arguments are validated. A schema that does not compile, or has no root `type: "object"`, fails the enable with `E_VALIDATION` ## Anti-patterns diff --git a/src/types/index.ts b/src/types/index.ts index 74cdcd2..2209dba 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,8 +6,10 @@ export type McpToolDefinition = { /** * Zod 4 raw shape (e.g. `{ symbol: z.string() }`) or Zod 4 object schema. A raw * shape is wrapped with `z.object()` before it reaches `McpServer.registerTool`; - * a Zod 3 schema is refused (`E_VALIDATION`). A plain JSON Schema object is - * registered as an open object: clients don't see its properties and args are not validated. + * a Zod 3 schema is refused (`E_VALIDATION`). A plain JSON Schema object + * (`type: "object"`) is registered with the SDK's `fromJsonSchema()`: clients see its + * properties and arguments are validated before the handler runs. A JSON Schema that + * cannot be compiled, or any other shape, fails the toolset's enable (`E_VALIDATION`). */ inputSchema: Record; handler: (args: any) => Promise | any; diff --git a/tests/dynamicToolManager.test.ts b/tests/dynamicToolManager.test.ts index 8b4989d..87fba02 100644 --- a/tests/dynamicToolManager.test.ts +++ b/tests/dynamicToolManager.test.ts @@ -322,7 +322,7 @@ describe("DynamicToolManager with a real McpServer", () => { expect((res.content as any)[0].text).toBe("AAPL"); }); - it("registers a plain JSON Schema inputSchema as a passthrough object", async () => { + it("registers a plain JSON Schema inputSchema and hands valid arguments to the handler", async () => { const { result, client } = await connect([ { name: "price", @@ -414,6 +414,154 @@ describe("DynamicToolManager input schema handling (Zod 4)", () => { }); }); +describe("DynamicToolManager JSON Schema inputSchema", () => { + const symbolSchema = { + type: "object", + properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1 } }, + required: ["symbol"], + additionalProperties: false, + }; + + function managerFor(inputSchema: unknown, handler = vi.fn(async (args: any) => ({ + content: [{ type: "text", text: String(args.symbol ?? "none") }], + }))) { + const server = new McpServer({ name: "t", version: "1.0.0" }); + const resolver = new ModuleResolver({ + catalog: { + core: { + name: "Core", + description: "", + tools: [{ name: "price", description: "Price", inputSchema, handler }], + }, + } as any, + }); + const manager = new DynamicToolManager({ + server, + resolver, + toolRegistry: new ToolRegistry({ namespaceWithToolset: false }), + }); + return { server, manager, handler }; + } + + async function connectClient(server: McpServer) { + const [serverSide, clientSide] = InMemoryTransport.createLinkedPair(); + await server.connect(serverSide); + const client = new Client({ name: "c", version: "1.0.0" }); + await client.connect(clientSide); + return client; + } + + it("advertises the declared properties and required list", async () => { + const { server, manager } = managerFor(symbolSchema); + expect((await manager.enableToolset("core")).success).toBe(true); + const client = await connectClient(server); + const { tools } = await client.listTools(); + expect(tools[0].inputSchema).toMatchObject({ + type: "object", + properties: { symbol: { type: "string" }, limit: { type: "integer", minimum: 1 } }, + required: ["symbol"], + additionalProperties: false, + }); + }); + + it.each([ + ["a missing required property", {}], + ["a wrong type", { symbol: 42 }], + ["a violated constraint", { symbol: "AAPL", limit: 0 }], + ["an extra property the schema forbids", { symbol: "AAPL", extra: true }], + ])("rejects %s without calling the handler", async (_label, args) => { + const { server, manager, handler } = managerFor(symbolSchema); + await manager.enableToolset("core"); + const client = await connectClient(server); + const res = await client.callTool({ name: "price", arguments: args }); + expect(res.isError).toBe(true); + expect((res.content as any)[0].text).toMatch(/invalid|validation/i); + expect(handler).not.toHaveBeenCalled(); + }); + + it("passes valid arguments through unchanged", async () => { + const { server, manager, handler } = managerFor(symbolSchema); + await manager.enableToolset("core"); + const client = await connectClient(server); + const res = await client.callTool({ + name: "price", + arguments: { symbol: "AAPL", limit: 3 }, + }); + expect(res.isError).toBeFalsy(); + expect(handler).toHaveBeenCalledWith({ symbol: "AAPL", limit: 3 }); + }); + + it("registers a tool without an inputSchema as an open object", async () => { + const { server, manager } = managerFor(undefined); + expect((await manager.enableToolset("core")).success).toBe(true); + const client = await connectClient(server); + const res = await client.callTool({ name: "price", arguments: { symbol: "X" } }); + expect((res.content as any)[0].text).toBe("X"); + }); + + it.each([ + ["an unknown type name", { type: "object", properties: { a: { type: "strng" } } }], + ["an unresolved $ref", { type: "object", properties: { a: { $ref: "#/$defs/missing" } } }], + ["an unsupported $schema dialect", { $schema: "http://example.com/other", type: "object" }], + ])("fails the enable with E_VALIDATION on %s, and registers nothing", async (_label, schema) => { + const { server, manager } = managerFor(schema); + const result = await manager.enableToolset("core"); + expect(result.success).toBe(false); + expect(result.code).toBe("E_VALIDATION"); + expect(result.message).toMatch(/Tool 'price' has an invalid JSON Schema inputSchema/); + expect(manager.isActive("core")).toBe(false); + const client = await connectClient(server); + expect((await client.listTools()).tools).toEqual([]); + }); + + it.each([ + ["a JSON Schema whose root is not an object", { type: "string" }], + ["a JSON Schema without a root type", { properties: { a: { type: "string" } } }], + ["a shape that mixes Zod and non-Zod values", { a: z.string(), b: 5 }], + ])("fails the enable with E_VALIDATION on %s", async (_label, schema) => { + const { manager } = managerFor(schema); + const result = await manager.enableToolset("core"); + expect(result.success).toBe(false); + expect(result.code).toBe("E_VALIDATION"); + expect(result.message).toMatch(/neither a Zod 4 shape nor a JSON Schema object/); + }); + + it("keeps validators apart per manager: the same $id with different schemas never crosses over", async () => { + const schemaFor = (type: "string" | "number") => ({ + $id: "https://example.com/price-args", + type: "object", + properties: { value: { type }, }, + required: ["value"], + }); + const a = managerFor(schemaFor("string")); + const b = managerFor(schemaFor("number")); + await a.manager.enableToolset("core"); + await b.manager.enableToolset("core"); + const clientA = await connectClient(a.server); + const clientB = await connectClient(b.server); + + const aString = await clientA.callTool({ name: "price", arguments: { value: "x" } }); + const aNumber = await clientA.callTool({ name: "price", arguments: { value: 1 } }); + const bString = await clientB.callTool({ name: "price", arguments: { value: "x" } }); + const bNumber = await clientB.callTool({ name: "price", arguments: { value: 1 } }); + + expect(aString.isError).toBeFalsy(); + expect(aNumber.isError).toBe(true); + expect(bString.isError).toBe(true); + expect(bNumber.isError).toBeFalsy(); + }); + + it("re-enables a JSON Schema toolset with a $id after disabling it", async () => { + const { server, manager } = managerFor({ ...symbolSchema, $id: "https://example.com/symbol" }); + expect((await manager.enableToolset("core")).success).toBe(true); + expect((await manager.disableToolset("core")).success).toBe(true); + expect((await manager.enableToolset("core")).success).toBe(true); + const client = await connectClient(server); + const res = await client.callTool({ name: "price", arguments: { symbol: "AAPL" } }); + expect(res.isError).toBeFalsy(); + }); +}); + describe("DynamicToolManager disable removes tools", () => { const ping = { name: "ping", diff --git a/tests/e2e/jsonSchemaTools.e2e.test.ts b/tests/e2e/jsonSchemaTools.e2e.test.ts index 486ccc0..e0654e5 100644 --- a/tests/e2e/jsonSchemaTools.e2e.test.ts +++ b/tests/e2e/jsonSchemaTools.e2e.test.ts @@ -55,6 +55,11 @@ describe("JSON Schema tool definitions E2E", () => { const price = tools.tools.find((t) => t.name === "quotes.price"); expect(price).toBeDefined(); expect(price?.annotations).toEqual({ readOnlyHint: true }); + expect(price?.inputSchema).toMatchObject({ + type: "object", + properties: { symbol: { type: "string" } }, + required: ["symbol"], + }); const result = await client.callTool({ name: "quotes.price", @@ -64,4 +69,22 @@ describe("JSON Schema tool definitions E2E", () => { await client.close(); }); + + it("rejects arguments that break the schema, for that call only", async () => { + const transport = new StreamableHTTPClientTransport( + new URL(`http://localhost:${port}/mcp`), + { requestInit: { headers: { "mcp-client-id": "json-schema-invalid-client" } } } + ); + const client = new Client({ name: "test", version: "1.0.0" }); + await client.connect(transport); + + const bad = await client.callTool({ name: "quotes.price", arguments: { symbol: 42 } }); + expect(bad.isError).toBe(true); + expect(extractTextContent(bad)).toMatch(/invalid|validation/i); + + const good = await client.callTool({ name: "quotes.price", arguments: { symbol: "MSFT" } }); + expect(extractTextContent(good)).toBe("MSFT: 123.45"); + + await client.close(); + }); }); From 51f89493619d9d2094db554aa29ba5239e8047ae Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 12:05:41 +0000 Subject: [PATCH 4/8] docs: update changelog advisory note for vite-plugin-dts 5 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGSYMuTcM7fquxzRook --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aca200..0da36b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Releases before 0.7.0 are described in [GitHub Releases](https://github.com/code ### Fixed -- Dependency advisories: Zod 4.6.7 clears the high-severity `zod` finding (GHSA-mrhx-c7xc-m3w5); `body-parser`, `qs` and `ip-address` leave the tree with the v1 SDK's Express dependencies; `fastify` 5.12.8 and `fast-uri` 3.1.9 come from a lockfile refresh. Only the dev-only `minimatch` chain through `vite-plugin-dts` 4 remains, and it does not ship in the package. +- Dependency advisories: Zod 4.6.7 clears the high-severity `zod` finding (GHSA-mrhx-c7xc-m3w5); `body-parser`, `qs` and `ip-address` leave the tree with the v1 SDK's Express dependencies; `fastify` 5.12.8 and `fast-uri` 3.1.9 come from a lockfile refresh. `vite-plugin-dts` 5 and a dev-only `minimatch` override clear the last dev-only audit findings; none of this ships in the package. ## [0.8.0] From ba60dfe8f133b0cb79834a7be77aa86f802d3eeb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 12:10:04 +0000 Subject: [PATCH 5/8] fix: address TS review round 1 on JSON Schema tools Drop the root $id before compiling so tools sharing one $id keep their own validator, make the per-manager validator an eager field, pin test assertions, and add rollback, same-$id and concurrent-client tests. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTprrczjscL9XyWxYz --- CHANGELOG.md | 4 +- src/core/AGENTS.md | 4 +- src/core/DynamicToolManager.ts | 29 +++++---- tests/dynamicToolManager.test.ts | 93 +++++++++++++++++++++++---- tests/e2e/jsonSchemaTools.e2e.test.ts | 88 ++++++++++++++++++++++++- 5 files changed, 189 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2fa3a..8ce97b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,8 @@ Releases before 0.7.0 are described in [GitHub Releases](https://github.com/code - `FastifyTransport` and `PermissionAwareFastifyTransport` use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. Session handling, the per-client `mcp-client-id` model and the HTTP endpoints are unchanged: each client still gets its own server, transport and enabled/disabled toolset state. - A tool whose `inputSchema` is a plain JSON Schema object (`type: "object"`) is now registered with the SDK's `fromJsonSchema()`: clients see its real properties and arguments are validated against it before your handler runs. Until now such a tool registered as an open object, so its parameters were not advertised and any arguments reached the handler. Invalid arguments now get an error result and the handler is not called. - **Migration:** if a handler relied on receiving arguments its schema forbids (a missing `required` property, a wrong type, or extra properties under `additionalProperties: false`), loosen the schema or fix the caller. - - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool, instead of failing at `tools/list`. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (no root `type: "object"`, or a shape that mixes Zod and non-Zod values), which used to register as an open object. - - Each client's `DynamicToolManager` compiles JSON Schemas with its own validator, so one client's schema never answers for another's, even when both declare the same `$id`. + - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (no root `type: "object"`, or a shape that mixes Zod and non-Zod values), which used to register as an open object. + - Each client's `DynamicToolManager` compiles JSON Schemas with its own validator, so compiled schemas are not shared between clients and are freed with the client. The root `$id` of a tool's schema is not used or advertised (the validator caches by root `$id`, which would give two tools with the same `$id` the first one's validator); reference the root with `#`, not its `$id` URL. ### Fixed diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index cbd82c1..6a11fb3 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -18,9 +18,9 @@ Central orchestration layer. Manages toolset lifecycle, tool registration, and s 4. **Namespacing applied BEFORE collision check** — `mapAndValidate()` generates safe names first, then checks for conflicts. 5. **Toolset added to activeToolsets AFTER registration** — All tools must register successfully before toolset is marked active. A partial failure removes the tools already registered, so nothing is left callable and the toolset can be enabled again. 6. **`enableToolsets()` batches notifications** — Calls `enableToolset(name, skipNotification=true)` per toolset, sends one `tools/list_changed` notification at the end. The SDK also sends its own `tools/list_changed` for every `registerTool()` and `remove()` call on a connected server, so a disable of N tools sends N+1. -7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing schema registers as `z.looseObject({})`; anything else (no root `type: "object"`, a shape mixing Zod and non-Zod values, a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails instead of `tools/list`. +7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing or non-object schema registers as `z.looseObject({})`; anything else (no root `type: "object"`, a shape mixing Zod and non-Zod values, a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails. The root `$id` is dropped before compiling (the validator caches by `$id`, so two tools sharing one would share the first one's validator). 8. **Failed tool registration at startup makes the orchestrator not ready** — `enableToolsets()` reports failures in its result and never throws. A failure that carries a `code` (set when enabling threw, normally while registering the toolset's tools, e.g. a tool name collision) becomes `initError`, so `isReady()` is `false` and `ensureReady()` throws. Refusals made before any registration (invalid name, already enabled, exposure policy) carry no code and are ignored. Module loader errors are also ignored: `ModuleResolver` logs them and continues. -9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) lazily creates its own `AjvJsonSchemaValidator`. The SDK's shared default validator caches compiled schemas by `$id` and by object for the whole process, so one client's schema could answer for another's and the cache would grow with every client. Do not switch to `fromJsonSchema(schema)` without a validator argument. +9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) owns its own `AjvJsonSchemaValidator`. Ajv keeps every compiled schema for the validator's lifetime, so the SDK's process-wide default validator would hold all clients' schemas until exit; a per-manager one is freed with the client and shares nothing. Ajv's cache is never evicted when a toolset is disabled, so a client that toggles a loader-backed toolset many times grows its own cache until the session ends; that growth is bounded by the client's lifetime. Do not switch to `fromJsonSchema(schema)` without a validator argument. ## Enable Toolset Flow diff --git a/src/core/DynamicToolManager.ts b/src/core/DynamicToolManager.ts index c035cbf..d334394 100644 --- a/src/core/DynamicToolManager.ts +++ b/src/core/DynamicToolManager.ts @@ -31,12 +31,12 @@ export class DynamicToolManager { /** SDK handles of each toolset's registered tools, used to unregister them. */ private readonly toolHandles = new Map(); /** - * Validator for JSON Schema tools, created on first use. It is deliberately - * per manager (so per client): the SDK's shared default validator caches - * compiled schemas by `$id` and by object for the life of the process, which - * would let one client's schema answer for another's and grow with every client. + * Compiles the JSON Schemas of this manager's tools. It is deliberately per + * manager (so per client): Ajv keeps every compiled schema for the validator's + * lifetime, so the SDK's process-wide default validator would hold all clients' + * schemas until exit. Here they are freed with the client, and nothing is shared. */ - private jsonSchemaValidator?: jsonSchemaValidator; + private readonly schemaValidator = new AjvJsonSchemaValidator(); constructor(options: DynamicToolManagerOptions) { this.server = options.server; @@ -265,7 +265,7 @@ export class DynamicToolManager { const inputSchema = toRegistrableInputSchema( tool.name, tool.inputSchema, - () => (this.jsonSchemaValidator ??= new AjvJsonSchemaValidator()) + this.schemaValidator ); const handle = this.server.registerTool( @@ -449,11 +449,15 @@ function isZod3Type(value: unknown): boolean { * - A plain JSON Schema object (`type: "object"`) goes through the SDK's * `fromJsonSchema()`: clients see its real properties and arguments are * validated against it before the handler runs. - * - A missing schema registers as an open object, so arguments still reach the handler. + * The root `$id` is dropped before compiling (so it is not advertised either): the + * validator caches compiled schemas by `$id`, which would give two tools with the same + * `$id` the first tool's validator. Reference the root with `#` instead of its `$id` URL. + * - A missing or non-object schema registers as an open object, so arguments still + * reach the handler. * * @param toolName - The tool name, used in error messages * @param schema - The tool's inputSchema - * @param getValidator - Supplies the JSON Schema validator; called only for JSON Schema tools + * @param validator - Compiles JSON Schema tools; scoped to one client by the caller * @returns A schema to pass to `registerTool` * @throws ToolingError `E_VALIDATION` when the schema or a shape value is a Zod 3 schema, * when the schema is neither a Zod 4 shape nor a JSON Schema object, or when the JSON @@ -462,7 +466,7 @@ function isZod3Type(value: unknown): boolean { function toRegistrableInputSchema( toolName: string, schema: unknown, - getValidator: () => jsonSchemaValidator + validator: jsonSchemaValidator ): InputSchema { if (typeof schema !== "object" || schema === null) { return z.looseObject({}) as InputSchema; @@ -488,11 +492,10 @@ function toRegistrableInputSchema( "E_VALIDATION" ); } + const compilable: JsonSchemaType = { ...(schema as JsonSchemaType) }; + delete compilable.$id; try { - return fromJsonSchema>( - schema as JsonSchemaType, - getValidator() - ); + return fromJsonSchema>(compilable, validator); } catch (error) { throw new ToolingError( `Tool '${toolName}' has an invalid JSON Schema inputSchema: ${ diff --git a/tests/dynamicToolManager.test.ts b/tests/dynamicToolManager.test.ts index 423c974..4e47cdd 100644 --- a/tests/dynamicToolManager.test.ts +++ b/tests/dynamicToolManager.test.ts @@ -472,17 +472,17 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { }); it.each([ - ["a missing required property", {}], - ["a wrong type", { symbol: 42 }], - ["a violated constraint", { symbol: "AAPL", limit: 0 }], - ["an extra property the schema forbids", { symbol: "AAPL", extra: true }], - ])("rejects %s without calling the handler", async (_label, args) => { + ["a missing required property", {}, /required property 'symbol'/], + ["a wrong type", { symbol: 42 }, /symbol must be string/], + ["a violated constraint", { symbol: "AAPL", limit: 0 }, /limit must be >= 1/], + ["an extra property the schema forbids", { symbol: "AAPL", extra: true }, /additional properties/], + ])("rejects %s without calling the handler", async (_label, args, detail) => { const { server, manager, handler } = managerFor(symbolSchema); await manager.enableToolset("core"); const client = await connectClient(server); const res = await client.callTool({ name: "price", arguments: args }); expect(res.isError).toBe(true); - expect((res.content as any)[0].text).toMatch(/invalid|validation/i); + expect((res.content as any)[0].text).toMatch(detail); expect(handler).not.toHaveBeenCalled(); }); @@ -533,17 +533,17 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { expect(result.message).toMatch(/neither a Zod 4 shape nor a JSON Schema object/); }); - it("keeps validators apart per manager: the same $id with different schemas never crosses over", async () => { + it("validates each manager's client against its own schema, even for identical nested $id values", async () => { const schemaFor = (type: "string" | "number") => ({ - $id: "https://example.com/price-args", type: "object", - properties: { value: { type }, }, + properties: { value: { $ref: "#/$defs/value" } }, required: ["value"], + $defs: { value: { $id: "https://example.com/value", type } }, }); const a = managerFor(schemaFor("string")); const b = managerFor(schemaFor("number")); - await a.manager.enableToolset("core"); - await b.manager.enableToolset("core"); + expect((await a.manager.enableToolset("core")).success).toBe(true); + expect((await b.manager.enableToolset("core")).success).toBe(true); const clientA = await connectClient(a.server); const clientB = await connectClient(b.server); @@ -558,6 +558,77 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { expect(bNumber.isError).toBeFalsy(); }); + it("validates two tools of one manager against their own schema when both declare the same root $id", async () => { + const server = new McpServer({ name: "t", version: "1.0.0" }); + const echo = (args: any) => ({ content: [{ type: "text", text: String(args.value) }] }); + const schemaFor = (type: "string" | "number") => ({ + $id: "https://example.com/args", + type: "object", + properties: { value: { type } }, + required: ["value"], + }); + const resolver = new ModuleResolver({ + catalog: { + core: { + name: "Core", + description: "", + tools: [ + { name: "text", description: "Text", inputSchema: schemaFor("string"), handler: echo }, + { name: "count", description: "Count", inputSchema: schemaFor("number"), handler: echo }, + ], + }, + } as any, + }); + const manager = new DynamicToolManager({ + server, + resolver, + toolRegistry: new ToolRegistry({ namespaceWithToolset: false }), + }); + expect((await manager.enableToolset("core")).success).toBe(true); + const client = await connectClient(server); + + expect((await client.callTool({ name: "text", arguments: { value: "x" } })).isError).toBeFalsy(); + expect((await client.callTool({ name: "text", arguments: { value: 1 } })).isError).toBe(true); + expect((await client.callTool({ name: "count", arguments: { value: 1 } })).isError).toBeFalsy(); + expect((await client.callTool({ name: "count", arguments: { value: "x" } })).isError).toBe(true); + // The root $id is neither used nor advertised. + const { tools } = await client.listTools(); + expect(tools.every((t) => !("$id" in t.inputSchema))).toBe(true); + }); + + it("rolls back the tools registered before a bad JSON Schema tool", async () => { + const server = new McpServer({ name: "t", version: "1.0.0" }); + const resolver = new ModuleResolver({ + catalog: { + core: { + name: "Core", + description: "", + tools: [ + { name: "good", description: "Good", inputSchema: symbolSchema, handler: async () => ({ content: [] }) }, + { + name: "bad", + description: "Bad", + inputSchema: { type: "object", properties: { a: { type: "strng" } } }, + handler: async () => ({ content: [] }), + }, + ], + }, + } as any, + }); + const manager = new DynamicToolManager({ + server, + resolver, + toolRegistry: new ToolRegistry({ namespaceWithToolset: false }), + }); + const result = await manager.enableToolset("core"); + expect(result.success).toBe(false); + expect(result.code).toBe("E_VALIDATION"); + expect(result.message).toMatch(/Tool 'bad'/); + expect(manager.isActive("core")).toBe(false); + const client = await connectClient(server); + expect((await client.listTools()).tools).toEqual([]); + }); + it("re-enables a JSON Schema toolset with a $id after disabling it", async () => { const { server, manager } = managerFor({ ...symbolSchema, $id: "https://example.com/symbol" }); expect((await manager.enableToolset("core")).success).toBe(true); diff --git a/tests/e2e/jsonSchemaTools.e2e.test.ts b/tests/e2e/jsonSchemaTools.e2e.test.ts index e0654e5..ff1f515 100644 --- a/tests/e2e/jsonSchemaTools.e2e.test.ts +++ b/tests/e2e/jsonSchemaTools.e2e.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { createMcpServer } from "../../src/index.js"; import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client"; import { McpServer } from "@modelcontextprotocol/server"; -import { getAvailablePort, extractTextContent } from "./helpers.js"; +import { getAvailablePort, extractTextContent, extractToolNames, parseToolResponse } from "./helpers.js"; describe("JSON Schema tool definitions E2E", () => { let server: Awaited>; @@ -88,3 +88,89 @@ describe("JSON Schema tool definitions E2E", () => { await client.close(); }); }); + +describe("JSON Schema tools with concurrent clients E2E", () => { + let server: Awaited>; + let port: number; + /** Same root and nested `$id`, different types: each client must be validated by its own toolset. */ + const schemaFor = (type: "string" | "number") => ({ + $id: "https://example.com/args", + type: "object", + properties: { value: { $ref: "#/$defs/value" } }, + required: ["value"], + $defs: { value: { $id: "https://example.com/value", type } }, + }); + const echo = async ({ value }: { value: unknown }) => ({ + content: [{ type: "text" as const, text: String(value) }], + }); + + beforeAll(async () => { + port = await getAvailablePort(); + server = await createMcpServer({ + createServer: () => new McpServer({ name: "test-json-schema-multi", version: "1.0.0" }), + catalog: { + texts: { + name: "Texts", + description: "String tools", + tools: [{ name: "echo", description: "Echo", inputSchema: schemaFor("string"), handler: echo }], + }, + numbers: { + name: "Numbers", + description: "Number tools", + tools: [{ name: "echo", description: "Echo", inputSchema: schemaFor("number"), handler: echo }], + }, + }, + startup: { mode: "DYNAMIC" }, + http: { port }, + }); + await server.start(); + }); + + afterAll(async () => { + await server.close(); + }); + + async function connect(clientId: string) { + const client = new Client({ name: "test", version: "1.0.0" }); + await client.connect( + new StreamableHTTPClientTransport(new URL(`http://localhost:${port}/mcp`), { + requestInit: { headers: { "mcp-client-id": clientId } }, + }) + ); + return client; + } + + const enable = async (client: Client, name: string) => + parseToolResponse<{ success: boolean }>( + await client.callTool({ name: "enable_toolset", arguments: { name } } as any) + ); + + it("keeps each client's toolsets and validators apart", async () => { + const a = await connect("json-schema-a"); + const b = await connect("json-schema-b"); + expect((await enable(a, "texts")).success).toBe(true); + expect((await enable(b, "numbers")).success).toBe(true); + + expect(extractToolNames(await a.listTools())).toContain("texts.echo"); + expect(extractToolNames(await a.listTools())).not.toContain("numbers.echo"); + expect(extractToolNames(await b.listTools())).toContain("numbers.echo"); + expect(extractToolNames(await b.listTools())).not.toContain("texts.echo"); + + expect(extractTextContent(await a.callTool({ name: "texts.echo", arguments: { value: "x" } }))).toBe("x"); + expect((await a.callTool({ name: "texts.echo", arguments: { value: 1 } })).isError).toBe(true); + expect(extractTextContent(await b.callTool({ name: "numbers.echo", arguments: { value: 1 } }))).toBe("1"); + expect((await b.callTool({ name: "numbers.echo", arguments: { value: "x" } })).isError).toBe(true); + + // A disables its toolset; B keeps validating and serving its own. + const disabled = parseToolResponse<{ success: boolean }>( + await a.callTool({ name: "disable_toolset", arguments: { name: "texts" } } as any) + ); + expect(disabled.success).toBe(true); + expect(extractToolNames(await a.listTools())).not.toContain("texts.echo"); + expect(extractTextContent(await b.callTool({ name: "numbers.echo", arguments: { value: 2 } }))).toBe("2"); + + await a.close(); + await b.close(); + }); +}); + From c206ae183068ea8d8389bc002066ca4a79dc5210 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 12:15:03 +0000 Subject: [PATCH 6/8] fix: address TS review round 2 on JSON Schema tools Copy the schema only when a root $id must be dropped so Ajv's identity cache still hits, pin the per-manager validator with a constructor spy, tighten docs and assertions, and cover absolute $refs to a nested $id. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTprrczjscL9XyWxYz --- CHANGELOG.md | 2 +- src/core/AGENTS.md | 2 +- src/core/DynamicToolManager.ts | 13 ++++++--- tests/dynamicToolManager.test.ts | 17 ++++++++++- tests/e2e/jsonSchemaTools.e2e.test.ts | 6 ++-- tests/schemaValidatorScope.test.ts | 41 +++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 tests/schemaValidatorScope.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce97b7..7a3bb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Releases before 0.7.0 are described in [GitHub Releases](https://github.com/code - `FastifyTransport` and `PermissionAwareFastifyTransport` use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. Session handling, the per-client `mcp-client-id` model and the HTTP endpoints are unchanged: each client still gets its own server, transport and enabled/disabled toolset state. - A tool whose `inputSchema` is a plain JSON Schema object (`type: "object"`) is now registered with the SDK's `fromJsonSchema()`: clients see its real properties and arguments are validated against it before your handler runs. Until now such a tool registered as an open object, so its parameters were not advertised and any arguments reached the handler. Invalid arguments now get an error result and the handler is not called. - **Migration:** if a handler relied on receiving arguments its schema forbids (a missing `required` property, a wrong type, or extra properties under `additionalProperties: false`), loosen the schema or fix the caller. - - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (no root `type: "object"`, or a shape that mixes Zod and non-Zod values), which used to register as an open object. + - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (the root `type` must be the string `"object"`, so `type: ["object", "null"]` is refused; this includes a shape that mixes Zod and non-Zod values), which used to register as an open object. - Each client's `DynamicToolManager` compiles JSON Schemas with its own validator, so compiled schemas are not shared between clients and are freed with the client. The root `$id` of a tool's schema is not used or advertised (the validator caches by root `$id`, which would give two tools with the same `$id` the first one's validator); reference the root with `#`, not its `$id` URL. ### Fixed diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index 6a11fb3..1623706 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -18,7 +18,7 @@ Central orchestration layer. Manages toolset lifecycle, tool registration, and s 4. **Namespacing applied BEFORE collision check** — `mapAndValidate()` generates safe names first, then checks for conflicts. 5. **Toolset added to activeToolsets AFTER registration** — All tools must register successfully before toolset is marked active. A partial failure removes the tools already registered, so nothing is left callable and the toolset can be enabled again. 6. **`enableToolsets()` batches notifications** — Calls `enableToolset(name, skipNotification=true)` per toolset, sends one `tools/list_changed` notification at the end. The SDK also sends its own `tools/list_changed` for every `registerTool()` and `remove()` call on a connected server, so a disable of N tools sends N+1. -7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing or non-object schema registers as `z.looseObject({})`; anything else (no root `type: "object"`, a shape mixing Zod and non-Zod values, a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails. The root `$id` is dropped before compiling (the validator caches by `$id`, so two tools sharing one would share the first one's validator). +7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing or non-object schema registers as `z.looseObject({})`; anything else (a root `type` that is not the string `"object"`, which includes a shape mixing Zod and non-Zod values; a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails. The root `$id` is dropped before compiling (the validator caches by `$id`, so two tools sharing one would share the first one's validator). 8. **Failed tool registration at startup makes the orchestrator not ready** — `enableToolsets()` reports failures in its result and never throws. A failure that carries a `code` (set when enabling threw, normally while registering the toolset's tools, e.g. a tool name collision) becomes `initError`, so `isReady()` is `false` and `ensureReady()` throws. Refusals made before any registration (invalid name, already enabled, exposure policy) carry no code and are ignored. Module loader errors are also ignored: `ModuleResolver` logs them and continues. 9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) owns its own `AjvJsonSchemaValidator`. Ajv keeps every compiled schema for the validator's lifetime, so the SDK's process-wide default validator would hold all clients' schemas until exit; a per-manager one is freed with the client and shares nothing. Ajv's cache is never evicted when a toolset is disabled, so a client that toggles a loader-backed toolset many times grows its own cache until the session ends; that growth is bounded by the client's lifetime. Do not switch to `fromJsonSchema(schema)` without a validator argument. diff --git a/src/core/DynamicToolManager.ts b/src/core/DynamicToolManager.ts index d334394..0394026 100644 --- a/src/core/DynamicToolManager.ts +++ b/src/core/DynamicToolManager.ts @@ -450,8 +450,8 @@ function isZod3Type(value: unknown): boolean { * `fromJsonSchema()`: clients see its real properties and arguments are * validated against it before the handler runs. * The root `$id` is dropped before compiling (so it is not advertised either): the - * validator caches compiled schemas by `$id`, which would give two tools with the same - * `$id` the first tool's validator. Reference the root with `#` instead of its `$id` URL. + * validator caches compiled schemas by `$id`, which would give two tools with the + * same `$id` the first tool's validator. Reference the root with `#`, not its URL. * - A missing or non-object schema registers as an open object, so arguments still * reach the handler. * @@ -492,8 +492,13 @@ function toRegistrableInputSchema( "E_VALIDATION" ); } - const compilable: JsonSchemaType = { ...(schema as JsonSchemaType) }; - delete compilable.$id; + // Copy only when there is a root `$id` to drop: Ajv caches by object identity, + // so passing the caller's own object lets a re-enable reuse the compiled schema. + let compilable = schema as JsonSchemaType; + if (typeof compilable.$id === "string") { + compilable = { ...compilable }; + delete compilable.$id; + } try { return fromJsonSchema>(compilable, validator); } catch (error) { diff --git a/tests/dynamicToolManager.test.ts b/tests/dynamicToolManager.test.ts index 4e47cdd..cf0008f 100644 --- a/tests/dynamicToolManager.test.ts +++ b/tests/dynamicToolManager.test.ts @@ -525,6 +525,7 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { ["a JSON Schema whose root is not an object", { type: "string" }], ["a JSON Schema without a root type", { properties: { a: { type: "string" } } }], ["a shape that mixes Zod and non-Zod values", { a: z.string(), b: 5 }], + ["a root type array", { type: ["object", "null"] }], ])("fails the enable with E_VALIDATION on %s", async (_label, schema) => { const { manager } = managerFor(schema); const result = await manager.enableToolset("core"); @@ -533,7 +534,7 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { expect(result.message).toMatch(/neither a Zod 4 shape nor a JSON Schema object/); }); - it("validates each manager's client against its own schema, even for identical nested $id values", async () => { + it("validates two managers against their own schemas, even for identical nested $id values", async () => { const schemaFor = (type: "string" | "number") => ({ type: "object", properties: { value: { $ref: "#/$defs/value" } }, @@ -629,6 +630,20 @@ describe("DynamicToolManager JSON Schema inputSchema", () => { expect((await client.listTools()).tools).toEqual([]); }); + it("resolves absolute $refs to a nested $id while the root $id is dropped", async () => { + const { server, manager } = managerFor({ + $id: "https://example.com/root", + type: "object", + properties: { symbol: { $ref: "https://example.com/symbol" } }, + required: ["symbol"], + $defs: { symbol: { $id: "https://example.com/symbol", type: "string" } }, + }); + expect((await manager.enableToolset("core")).success).toBe(true); + const client = await connectClient(server); + expect((await client.callTool({ name: "price", arguments: { symbol: "AAPL" } })).isError).toBeFalsy(); + expect((await client.callTool({ name: "price", arguments: { symbol: 1 } })).isError).toBe(true); + }); + it("re-enables a JSON Schema toolset with a $id after disabling it", async () => { const { server, manager } = managerFor({ ...symbolSchema, $id: "https://example.com/symbol" }); expect((await manager.enableToolset("core")).success).toBe(true); diff --git a/tests/e2e/jsonSchemaTools.e2e.test.ts b/tests/e2e/jsonSchemaTools.e2e.test.ts index ff1f515..f73cceb 100644 --- a/tests/e2e/jsonSchemaTools.e2e.test.ts +++ b/tests/e2e/jsonSchemaTools.e2e.test.ts @@ -80,7 +80,7 @@ describe("JSON Schema tool definitions E2E", () => { const bad = await client.callTool({ name: "quotes.price", arguments: { symbol: 42 } }); expect(bad.isError).toBe(true); - expect(extractTextContent(bad)).toMatch(/invalid|validation/i); + expect(extractTextContent(bad)).toMatch(/Input validation error/); const good = await client.callTool({ name: "quotes.price", arguments: { symbol: "MSFT" } }); expect(extractTextContent(good)).toBe("MSFT: 123.45"); @@ -92,7 +92,7 @@ describe("JSON Schema tool definitions E2E", () => { describe("JSON Schema tools with concurrent clients E2E", () => { let server: Awaited>; let port: number; - /** Same root and nested `$id`, different types: each client must be validated by its own toolset. */ + /** Same root and nested `$id`, different types: each client is validated by its own toolset. */ const schemaFor = (type: "string" | "number") => ({ $id: "https://example.com/args", type: "object", @@ -145,7 +145,7 @@ describe("JSON Schema tools with concurrent clients E2E", () => { await client.callTool({ name: "enable_toolset", arguments: { name } } as any) ); - it("keeps each client's toolsets and validators apart", async () => { + it("keeps each client's toolsets and validation apart", async () => { const a = await connect("json-schema-a"); const b = await connect("json-schema-b"); expect((await enable(a, "texts")).success).toBe(true); diff --git a/tests/schemaValidatorScope.test.ts b/tests/schemaValidatorScope.test.ts new file mode 100644 index 0000000..e637be0 --- /dev/null +++ b/tests/schemaValidatorScope.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect, vi } from "vitest"; + +const constructed = vi.hoisted(() => ({ count: 0 })); + +vi.mock("@modelcontextprotocol/server/validators/ajv", async (importOriginal) => { + const original = + await importOriginal(); + return { + ...original, + AjvJsonSchemaValidator: class extends original.AjvJsonSchemaValidator { + constructor() { + super(); + constructed.count += 1; + } + }, + }; +}); + +import { McpServer } from "@modelcontextprotocol/server"; +import { DynamicToolManager } from "../src/core/DynamicToolManager.js"; +import { ModuleResolver } from "../src/mode/ModuleResolver.js"; + +/** + * JSON Schema validators hold every compiled schema for their lifetime, so each + * client's manager must own one instead of sharing a process-wide validator. + */ +describe("JSON Schema validator scope", () => { + function newManager() { + return new DynamicToolManager({ + server: new McpServer({ name: "t", version: "1.0.0" }), + resolver: new ModuleResolver({ catalog: {} }), + }); + } + + it("creates one validator per manager, not one per process or per tool", () => { + const before = constructed.count; + newManager(); + newManager(); + expect(constructed.count - before).toBe(2); + }); +}); From 86bff9e4c807b501017ccb7a3709920fe06a7e70 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 12:17:40 +0000 Subject: [PATCH 7/8] docs: note that a root $id copy adds a cache entry per enable Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTprrczjscL9XyWxYz --- src/core/AGENTS.md | 2 +- src/core/DynamicToolManager.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index 1623706..ec47ab9 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -20,7 +20,7 @@ Central orchestration layer. Manages toolset lifecycle, tool registration, and s 6. **`enableToolsets()` batches notifications** — Calls `enableToolset(name, skipNotification=true)` per toolset, sends one `tools/list_changed` notification at the end. The SDK also sends its own `tools/list_changed` for every `registerTool()` and `remove()` call on a connected server, so a disable of N tools sends N+1. 7. **`inputSchema` goes through `toRegistrableInputSchema()`** — A Zod 4 schema instance is used as is; a raw shape of Zod 4 values is wrapped with toolception's own `z.object()` (so the SDK never wraps it with a second copy of Zod); a Zod 3 schema throws `ToolingError` `E_VALIDATION` (SDK v2 cannot read it) and the enable fails; a JSON Schema object with root `type: "object"` goes through the SDK's `fromJsonSchema()`, so clients see the declared schema and arguments are validated before the handler runs; a missing or non-object schema registers as `z.looseObject({})`; anything else (a root `type` that is not the string `"object"`, which includes a shape mixing Zod and non-Zod values; a JSON Schema that fails to compile) throws `ToolingError` `E_VALIDATION` at registration, so the enable fails. The root `$id` is dropped before compiling (the validator caches by `$id`, so two tools sharing one would share the first one's validator). 8. **Failed tool registration at startup makes the orchestrator not ready** — `enableToolsets()` reports failures in its result and never throws. A failure that carries a `code` (set when enabling threw, normally while registering the toolset's tools, e.g. a tool name collision) becomes `initError`, so `isReady()` is `false` and `ensureReady()` throws. Refusals made before any registration (invalid name, already enabled, exposure policy) carry no code and are ignored. Module loader errors are also ignored: `ModuleResolver` logs them and continues. -9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) owns its own `AjvJsonSchemaValidator`. Ajv keeps every compiled schema for the validator's lifetime, so the SDK's process-wide default validator would hold all clients' schemas until exit; a per-manager one is freed with the client and shares nothing. Ajv's cache is never evicted when a toolset is disabled, so a client that toggles a loader-backed toolset many times grows its own cache until the session ends; that growth is bounded by the client's lifetime. Do not switch to `fromJsonSchema(schema)` without a validator argument. +9. **JSON Schema validators are per manager** — Each `DynamicToolManager` (one per client) owns its own `AjvJsonSchemaValidator`. Ajv keeps every compiled schema for the validator's lifetime, so the SDK's process-wide default validator would hold all clients' schemas until exit; a per-manager one is freed with the client and shares nothing. Ajv's cache is never evicted when a toolset is disabled, so a client that toggles a loader-backed toolset, or one whose schema has a root `$id` (copied on each enable), many times grows its own cache until the session ends; that growth is bounded by the client's lifetime. Do not switch to `fromJsonSchema(schema)` without a validator argument. ## Enable Toolset Flow diff --git a/src/core/DynamicToolManager.ts b/src/core/DynamicToolManager.ts index 0394026..9ec0a2e 100644 --- a/src/core/DynamicToolManager.ts +++ b/src/core/DynamicToolManager.ts @@ -493,7 +493,8 @@ function toRegistrableInputSchema( ); } // Copy only when there is a root `$id` to drop: Ajv caches by object identity, - // so passing the caller's own object lets a re-enable reuse the compiled schema. + // so a schema without one is passed as is and a re-enable reuses its compiled + // form. A copy is a new cache key, bounded by the client's lifetime. let compilable = schema as JsonSchemaType; if (typeof compilable.$id === "string") { compilable = { ...compilable }; From e70296cd9098b207d9de2a952e98a206dc0c84ab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 10:13:44 +0000 Subject: [PATCH 8/8] chore(release): 0.9.0 Version 0.9.0 in package.json and package-lock.json, and the [0.9.0] changelog entry for the MCP SDK v2 / Zod 4 move (#35), vite-plugin-dts 5 (#36), Node 26 CI (#37) and validated JSON Schema tools (#38). Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGSYMuTcM7fquxzRook --- CHANGELOG.md | 16 ++++++++++------ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0eda9..908cef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,25 +4,29 @@ All notable changes to `toolception` are listed here. Versions follow [semver](h Releases before 0.7.0 are described in [GitHub Releases](https://github.com/code-rabi/toolception/releases). -## [Unreleased] +## [0.9.0] ### Breaking -- Toolception now runs on MCP TypeScript SDK v2 and Zod 4. The `@modelcontextprotocol/sdk` dependency is replaced by `@modelcontextprotocol/server` and `@modelcontextprotocol/node`, and `zod` moves from ^3.25 to ^4.6.7. SDK v2 does not read Zod 3 schemas, so the two cannot be upgraded separately. +- Toolception now runs on MCP TypeScript SDK v2 and Zod 4. The `@modelcontextprotocol/sdk` dependency is replaced by `@modelcontextprotocol/server` and `@modelcontextprotocol/node`, and `zod` moves from ^3.25 to ^4.6.7. SDK v2 does not read Zod 3 schemas, so the two cannot be upgraded separately. ([#35](https://github.com/code-rabi/toolception/pull/35)) - **Migration:** change your imports to `@modelcontextprotocol/server` (`McpServer`) and `@modelcontextprotocol/client` (`Client`, `StreamableHTTPClientTransport`); install `zod` ^4.6.7; and write tool `inputSchema` values and `defineEndpoint` schemas with Zod 4. `z.record()` needs two arguments in Zod 4. Pass server capabilities as the second `McpServer` argument (`new McpServer(info, { capabilities })`); `capabilities` inside the first argument is rejected by the types. - A tool whose `inputSchema` is a Zod 3 schema (or a raw shape holding one) now fails to enable with an `E_VALIDATION` error that names the tool. - `createServer` factories must return a v2 `McpServer`; the removed `server.tool()`, `server.prompt()` and `server.resource()` are replaced by `registerTool`, `registerPrompt` and `registerResource`. - `defineEndpoint` / `definePermissionAwareEndpoint` validation failures (`VALIDATION_ERROR`, HTTP 400) now carry Zod 4 issue objects in `error.details`: issue codes and messages differ from Zod 3 (for example `invalid_type` messages). Clients that match on the old text must be updated. -- Calling a tool the server no longer lists (for example a disabled toolset's tool) is now answered with a JSON-RPC error ("Tool ... not found") instead of a result with `isError: true`. This is SDK v2 behaviour. -- `FastifyTransport` and `PermissionAwareFastifyTransport` use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. Session handling, the per-client `mcp-client-id` model and the HTTP endpoints are unchanged: each client still gets its own server, transport and enabled/disabled toolset state. -- A tool whose `inputSchema` is a plain JSON Schema object (`type: "object"`) is now registered with the SDK's `fromJsonSchema()`: clients see its real properties and arguments are validated against it before your handler runs. Until now such a tool registered as an open object, so its parameters were not advertised and any arguments reached the handler. Invalid arguments now get an error result and the handler is not called. +- Calling a tool the server no longer lists (for example a disabled toolset's tool) is now answered with a JSON-RPC error ("Tool ... not found") instead of a result with `isError: true`. This is SDK v2 behaviour. ([#35](https://github.com/code-rabi/toolception/pull/35)) +- `FastifyTransport` and `PermissionAwareFastifyTransport` use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`. Session handling, the per-client `mcp-client-id` model and the HTTP endpoints are unchanged: each client still gets its own server, transport and enabled/disabled toolset state. ([#35](https://github.com/code-rabi/toolception/pull/35)) +- A tool whose `inputSchema` is a plain JSON Schema object (`type: "object"`) is now registered with the SDK's `fromJsonSchema()`: clients see its real properties and arguments are validated against it before your handler runs. Until now such a tool registered as an open object, so its parameters were not advertised and any arguments reached the handler. Invalid arguments now get an error result and the handler is not called. ([#38](https://github.com/code-rabi/toolception/pull/38)) - **Migration:** if a handler relied on receiving arguments its schema forbids (a missing `required` property, a wrong type, or extra properties under `additionalProperties: false`), loosen the schema or fix the caller. - A JSON Schema that cannot be compiled (unknown type name, unresolved `$ref`, unsupported `$schema` dialect) now fails the toolset's enable with an `E_VALIDATION` error that names the tool. So does an `inputSchema` that is neither a Zod 4 shape nor a JSON Schema object (the root `type` must be the string `"object"`, so `type: ["object", "null"]` is refused; this includes a shape that mixes Zod and non-Zod values), which used to register as an open object. - Each client's `DynamicToolManager` compiles JSON Schemas with its own validator, so compiled schemas are not shared between clients and are freed with the client. The root `$id` of a tool's schema is not used or advertised (the validator caches by root `$id`, which would give two tools with the same `$id` the first one's validator); reference the root with `#`, not its `$id` URL. ### Fixed -- Dependency advisories: Zod 4.6.7 clears the high-severity `zod` finding (GHSA-mrhx-c7xc-m3w5); `body-parser`, `qs` and `ip-address` leave the tree with the v1 SDK's Express dependencies; `fastify` 5.12.8 and `fast-uri` 3.1.9 come from a lockfile refresh. `vite-plugin-dts` 5 and a dev-only `minimatch` override clear the last dev-only audit findings; none of this ships in the package. +- Dependency advisories: Zod 4.6.7 clears the high-severity `zod` finding (GHSA-mrhx-c7xc-m3w5); `body-parser`, `qs` and `ip-address` leave the tree with the v1 SDK's Express dependencies; `fastify` 5.12.8 and `fast-uri` 3.1.9 come from a lockfile refresh. `vite-plugin-dts` 5 and a dev-only `minimatch` override clear the last dev-only audit findings; none of this ships in the package. ([#35](https://github.com/code-rabi/toolception/pull/35), [#36](https://github.com/code-rabi/toolception/pull/36)) + +### Changed + +- CI and the release workflow run on Node 26, because vitest 5 does not support Node 25. `engines` stays `>=25.3.0`, so consumers are unaffected. ([#37](https://github.com/code-rabi/toolception/pull/37)) ## [0.8.0] diff --git a/package-lock.json b/package-lock.json index e411507..1fb7579 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "toolception", - "version": "0.8.0", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "toolception", - "version": "0.8.0", + "version": "0.9.0", "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^10.0.1", diff --git a/package.json b/package.json index f6004be..7c93c17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "toolception", - "version": "0.8.0", + "version": "0.9.0", "private": false, "type": "module", "main": "dist/index.js",