diff --git a/package-lock.json b/package-lock.json index d32963a73..2d41ebcbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,8 @@ "eslint-plugin-n": "^17.23.1", "prettier": "3.6.2", "supertest": "^7.0.0", + "ts-morph": "^27.0.2", + "ts-to-zod": "^5.1.0", "tsx": "^4.16.5", "typescript": "^5.5.4", "typescript-eslint": "^8.48.1", @@ -72,6 +74,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@clack/core": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0-alpha.4.tgz", + "integrity": "sha512-VCtU+vjyKPMSakVrB9q1bOnXN7QW/w4+YQDQCOF59GrzydW+169i0fVx/qzRRXJgt8KGj/pZZ/JxXroFZIDByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0-alpha.4.tgz", + "integrity": "sha512-KnmtDF2xQGoI5AlBme9akHtvCRV0RKAARUXHBQO2tMwnY8B08/4zPWigT7uLK25UPrMCEqnyQPkKRjNdhPbf8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.0.0-alpha.4", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -714,6 +739,29 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -734,6 +782,78 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@oclif/core": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.8.0.tgz", + "integrity": "sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "ansis": "^3.17.0", + "clean-stack": "^3.0.1", + "cli-spinners": "^2.9.2", + "debug": "^4.4.3", + "ejs": "^3.1.10", + "get-package-type": "^0.1.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "lilconfig": "^3.1.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "string-width": "^4.2.3", + "supports-color": "^8", + "tinyglobby": "^0.2.14", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@oclif/core/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@oclif/core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@oclif/core/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -1059,6 +1179,34 @@ "dev": true, "license": "MIT" }, + "node_modules/@ts-morph/common": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", + "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.14" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -1319,7 +1467,6 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -1648,6 +1795,19 @@ "win32" ] }, + "node_modules/@typescript/vfs": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.2.tgz", + "integrity": "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/@vitest/expect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.8.tgz", @@ -1751,7 +1911,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1802,6 +1961,35 @@ } } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1817,6 +2005,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1841,6 +2039,13 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1962,6 +2167,108 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1980,6 +2287,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2153,6 +2467,29 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2176,6 +2513,19 @@ "node": ">=10.13.0" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2294,7 +2644,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2577,6 +2926,13 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", @@ -2613,7 +2969,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2719,6 +3074,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2872,6 +3260,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", @@ -2896,6 +3297,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3098,8 +3509,18 @@ "node": ">=0.8.19" } }, - "node_modules/inherits": { - "version": "2.0.4", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, @@ -3111,6 +3532,22 @@ "node": ">= 0.10" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3120,6 +3557,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3138,11 +3591,42 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jose": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.1.tgz", @@ -3211,6 +3695,93 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3232,6 +3803,98 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3319,6 +3982,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3412,6 +4088,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3481,6 +4173,13 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3645,6 +4344,20 @@ "node": ">= 0.10" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3673,6 +4386,30 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.53.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", @@ -3909,6 +4646,69 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3942,6 +4742,70 @@ "dev": true, "license": "MIT" }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4016,6 +4880,218 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/text-camel-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-camel-case/-/text-camel-case-1.2.9.tgz", + "integrity": "sha512-wKYs9SgRxYizJE1mneR7BbLNlGw2IYzJAS8XwkWIry0CTbO1gvvPkFsx5Z1/hr+VqUaBqx9q3yKd30HpZLdMsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-pascal-case": "1.2.9" + } + }, + "node_modules/text-capital-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-capital-case/-/text-capital-case-1.2.9.tgz", + "integrity": "sha512-X5zV8U8pxtq2xS2t46lgAWqZdDbgWMKq03MQSNwY2CJdQCsdTNh144E2Q/q9wBxWzSBUXn+jRc9kF+Gs8/pGhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9", + "text-upper-case-first": "1.2.9" + } + }, + "node_modules/text-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-case/-/text-case-1.2.9.tgz", + "integrity": "sha512-zZVdA8rMcjx9zhekdUuOPZShc25UTV7W8/ddKbgbPtfCEvIiToPtWiSd2lXLSuiGMovNhJ4+Tw49xll9o9ts+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-camel-case": "1.2.9", + "text-capital-case": "1.2.9", + "text-constant-case": "1.2.9", + "text-dot-case": "1.2.9", + "text-header-case": "1.2.9", + "text-is-lower-case": "1.2.9", + "text-is-upper-case": "1.2.9", + "text-kebab-case": "1.2.9", + "text-lower-case": "1.2.9", + "text-lower-case-first": "1.2.9", + "text-no-case": "1.2.9", + "text-param-case": "1.2.9", + "text-pascal-case": "1.2.9", + "text-path-case": "1.2.9", + "text-sentence-case": "1.2.9", + "text-snake-case": "1.2.9", + "text-swap-case": "1.2.9", + "text-title-case": "1.2.9", + "text-upper-case": "1.2.9", + "text-upper-case-first": "1.2.9" + } + }, + "node_modules/text-constant-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-constant-case/-/text-constant-case-1.2.9.tgz", + "integrity": "sha512-Vosm6nC7Gag+JFakJHwqS9AXRNgl07j5KZ7srU9cYuKRzYwrxzeJ4RpEogRBNHw7CfmOm0j5FGEznblWtu7pIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9", + "text-upper-case": "1.2.9" + } + }, + "node_modules/text-dot-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-dot-case/-/text-dot-case-1.2.9.tgz", + "integrity": "sha512-N83hsnvGdSO9q9AfNSB9Cy1LFDNN2MCx53LcxtaPoDWPUTk47fv0JlvIY1tgY0wyzCiThF03kVj3jworvAOScA==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9" + } + }, + "node_modules/text-header-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-header-case/-/text-header-case-1.2.9.tgz", + "integrity": "sha512-TqryEKcYisQAfWLbtT3xPnZlMZ/mySO1uS+LUg+B0eNuqgETrSzVpXIUj5E6Zf/EyJHgpZf4VndbAXtOMJuT4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-capital-case": "1.2.9" + } + }, + "node_modules/text-is-lower-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-is-lower-case/-/text-is-lower-case-1.2.9.tgz", + "integrity": "sha512-cEurrWSnYVYqL8FSwl5cK4mdfqF7qNDCcKJgXI3NnfTesiB8umxAhdlQoErrRYI1xEvYr2WN0MI333EehUhQjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-is-upper-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-is-upper-case/-/text-is-upper-case-1.2.9.tgz", + "integrity": "sha512-HxsWr3VCsXXiLlhD0c+Ey+mS2lOTCiSJbkepjaXNHl2bp33KiscQaiG0qLwQmmpZQm4SJCg2s9FkndxS0RNDLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-kebab-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-kebab-case/-/text-kebab-case-1.2.9.tgz", + "integrity": "sha512-nOUyNR5Ej2B9D/wyyXfwUEv26+pQuOb1pEX+ojE37mCIWo8QeOxw5y6nxuqDmG7NrEPzbO6265UMV+EICH13Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9" + } + }, + "node_modules/text-lower-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-lower-case/-/text-lower-case-1.2.9.tgz", + "integrity": "sha512-53AOnDrhPpiAUQkgY1SHleKUXp/u7GsqRX13NcCREZscmtjLLJ099uxMRjkK7q2KwHkFYVPl9ytkQlTkTQLS0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-lower-case-first": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-lower-case-first/-/text-lower-case-first-1.2.9.tgz", + "integrity": "sha512-iiphHTV7PVH0MljrEQUA9iBE7jfDpXoi4RQju3WzZU3BRVbS6540cNZgxR19hWa0z6z/7cJTH0Ls9LPBaiUfKg==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-no-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-no-case/-/text-no-case-1.2.9.tgz", + "integrity": "sha512-IcCt328KaapimSrytP4ThfC8URmHZb2DgOqCL9BYvGjpxY2lDiqCkIQk9sClZtwcELs2gTnq83a7jNc573FTLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-lower-case": "1.2.9" + } + }, + "node_modules/text-param-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-param-case/-/text-param-case-1.2.9.tgz", + "integrity": "sha512-nR/Ju9amY3aQS1en2CUCgqN/ZiZIVdDyjlJ3xX5J92ChBevGuA4o9K10fh3JGMkbzK97Vcb+bWQJ4Q+Svz+GyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-dot-case": "1.2.9" + } + }, + "node_modules/text-pascal-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-pascal-case/-/text-pascal-case-1.2.9.tgz", + "integrity": "sha512-o6ZxMGjWDTUW54pcghpXes+C2PqbYRMdU5mHrIhueb6z6nq1NueiIOeCUdrSjN/3wXfhCmnFjK7/d9aRGZNqSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9" + } + }, + "node_modules/text-path-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-path-case/-/text-path-case-1.2.9.tgz", + "integrity": "sha512-s8cJ6r5TkJp5ticXMgtxd7f12odEN4d1CfX5u4aoz6jcUtBR2lDqzIhVimkqWFMJ4UKPSrmilUha8Xc2BPi+ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-dot-case": "1.2.9" + } + }, + "node_modules/text-sentence-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-sentence-case/-/text-sentence-case-1.2.9.tgz", + "integrity": "sha512-/G/Yi5kZfUa1edFRV4O3lGZAkbDZTFvlwW8CYfH7szkEGe2k2MYEYbOyAkGRVQEGV6V6JiuUAaP3VS9c1tB6nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9", + "text-upper-case-first": "1.2.9" + } + }, + "node_modules/text-snake-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-snake-case/-/text-snake-case-1.2.9.tgz", + "integrity": "sha512-+ZrqK19ynF/TLQZ7ynqVrL2Dy04uu9syYZwsm8PhzUdsY3XrwPy6QiRqhIEFqhyWbShPcfyfmheer5UEQqFxlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-dot-case": "1.2.9" + } + }, + "node_modules/text-swap-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-swap-case/-/text-swap-case-1.2.9.tgz", + "integrity": "sha512-g5fp12ldktYKK9wdHRMvvtSCQrZYNv/D+ZGLumDsvAY4q9T5bCMO2IWMkIP1F5gVQrysdHH6Xv877P/pjUq1iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-title-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-title-case/-/text-title-case-1.2.9.tgz", + "integrity": "sha512-RAtC9cdmPp41ns5/HXZBsaQg71BsHT7uZpj2ojTtuFa8o2dNuRYYOrSmy5YdLRIAJQ6WK5hQVpV3jHuq7a+4Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-no-case": "1.2.9", + "text-upper-case-first": "1.2.9" + } + }, + "node_modules/text-upper-case": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-upper-case/-/text-upper-case-1.2.9.tgz", + "integrity": "sha512-K/0DNT7a4z8eah2spARtoJllTZyrNTo6Uc0ujhN/96Ir9uJ/slpahfs13y46H9osL3daaLl3O7iXOkW4xtX6bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-upper-case-first": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/text-upper-case-first/-/text-upper-case-first-1.2.9.tgz", + "integrity": "sha512-wEDD1B6XqJmEV+xEnBJd+2sBCHZ+7fvA/8Rv/o8+dAsp05YWjYP/kjB8sPH6zqzW0s6jtehIg4IlcKjcYxk2CQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4071,7 +5147,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4146,13 +5221,76 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/ts-morph": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", + "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.28.1", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/ts-to-zod": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ts-to-zod/-/ts-to-zod-5.1.0.tgz", + "integrity": "sha512-giqqlvRHunlJqG9tBL/KAO3wWIVZGF//mZiWLKm/fdQnKnz4EN2mtiK5cugN9slytBkdMEXQIaLvMzIScbhhFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/prompts": "1.0.0-alpha.4", + "@oclif/core": "^4.5.4", + "@typescript/vfs": "^1.5.0", + "chokidar": "^4.0.3", + "listr2": "^9.0.4", + "slash": "^5.1.0", + "text-case": "^1.2.4", + "tslib": "^2.3.1", + "tsutils": "^3.21.0", + "typescript": "^5.2.2", + "zod": "^4.1.5" + }, + "bin": { + "ts-to-zod": "bin/run" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, "node_modules/tsx": { "version": "4.19.3", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -4179,6 +5317,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -4198,7 +5349,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4393,7 +5543,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4407,7 +5556,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4508,6 +5656,19 @@ "node": ">=8" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4517,6 +5678,54 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4556,11 +5765,10 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index bfbc73802..eedf3e9b3 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,9 @@ ], "scripts": { "fetch:spec-types": "tsx scripts/fetch-spec-types.ts", + "generate:schemas": "tsx scripts/generate-schemas.ts", "typecheck": "tsgo --noEmit", - "build": "npm run build:esm && npm run build:cjs", + "build": "tsx scripts/generate-schemas.ts --if-changed && npm run build:esm && npm run build:cjs", "build:esm": "mkdir -p dist/esm && echo '{\"type\": \"module\"}' > dist/esm/package.json && tsc -p tsconfig.prod.json", "build:esm:w": "npm run build:esm -- -w", "build:cjs": "mkdir -p dist/cjs && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && tsc -p tsconfig.cjs.json", @@ -78,7 +79,8 @@ "prepack": "npm run build:esm && npm run build:cjs", "lint": "eslint src/ && prettier --check .", "lint:fix": "eslint src/ --fix && prettier --write .", - "check": "npm run typecheck && npm run lint", + "check": "npm run typecheck && npm run lint && npm run check:schemas", + "check:schemas": "tsx scripts/check-schemas-sync.ts", "test": "vitest run", "test:watch": "vitest", "start": "npm run server", @@ -132,6 +134,8 @@ "eslint-plugin-n": "^17.23.1", "prettier": "3.6.2", "supertest": "^7.0.0", + "ts-morph": "^27.0.2", + "ts-to-zod": "^5.1.0", "tsx": "^4.16.5", "typescript": "^5.5.4", "typescript-eslint": "^8.48.1", diff --git a/scripts/check-schemas-sync.ts b/scripts/check-schemas-sync.ts new file mode 100644 index 000000000..a1e55c439 --- /dev/null +++ b/scripts/check-schemas-sync.ts @@ -0,0 +1,72 @@ +#!/usr/bin/env npx tsx +/** + * Checks if generated schemas are in sync with source types. + * Exits with code 1 if regeneration would produce different output. + * + * Usage: + * npx tsx scripts/check-schemas-sync.ts + * npm run check:schemas + */ + +import { execSync } from 'child_process'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +const GENERATED_FILES = ['src/generated/sdk.types.ts', 'src/generated/sdk.schemas.ts']; + +function main(): void { + const rootDir = join(import.meta.dirname, '..'); + + // Capture current content of generated files + const originalContents = new Map(); + for (const file of GENERATED_FILES) { + const filePath = join(rootDir, file); + try { + originalContents.set(file, readFileSync(filePath, 'utf-8')); + } catch { + console.error(`Error: Generated file ${file} does not exist.`); + console.error("Run 'npm run generate:schemas' to generate it."); + process.exit(1); + } + } + + // Regenerate schemas + console.log('Regenerating schemas to check for drift...'); + try { + execSync('npm run generate:schemas', { + cwd: rootDir, + stdio: 'pipe' + }); + } catch (error) { + console.error('Error: Schema generation failed.'); + console.error((error as Error).message); + process.exit(1); + } + + // Compare with original content + let hasDrift = false; + for (const file of GENERATED_FILES) { + const filePath = join(rootDir, file); + const newContent = readFileSync(filePath, 'utf-8'); + const originalContent = originalContents.get(file)!; + + if (newContent !== originalContent) { + console.error(`\n❌ ${file} is out of sync with source types.`); + hasDrift = true; + } else { + console.log(`✓ ${file} is up to date.`); + } + } + + if (hasDrift) { + console.error('\n' + '='.repeat(60)); + console.error('Generated schemas are out of sync!'); + console.error("Run 'npm run generate:schemas' and commit the changes."); + console.error('='.repeat(60)); + process.exit(1); + } + + console.log('\n✓ All generated schemas are in sync.'); +} + +main(); diff --git a/scripts/generate-schemas.ts b/scripts/generate-schemas.ts new file mode 100644 index 000000000..272d3f65a --- /dev/null +++ b/scripts/generate-schemas.ts @@ -0,0 +1,1582 @@ +/** + * Schema Generation Script + * + * Generates Zod schemas from spec.types.ts using ts-to-zod, with declarative + * transform pipelines for SDK compatibility. + * + * ## Pipeline + * + * ### Phase 1: Type Transforms (TYPE_TRANSFORMS) + * Transform spec.types.ts → sdk.types.ts for SDK conventions: + * - `extends JSONRPCRequest` → `extends Request` + * - `extends JSONRPCNotification` → `extends Notification` + * - Inject SDK-specific extensions (meta keys, capability types) + * - Convert JSDoc to @description for .describe() generation + * + * ### Phase 2: Type Cleanup (TYPE_CLEANUP_TRANSFORMS) + * Prepare types for clean SDK export: + * - Remove index signatures (enables TypeScript union narrowing) + * - Create union types (McpRequest, McpNotification, McpResult) + * + * ### Phase 3: Schema Transforms (SchemaTransforms) + * Transform ts-to-zod output for Zod v4 and SDK conventions: + * - Convert to Zod v4 imports and patterns + * - Add field-level validation (datetime, base64, etc.) + * - Add .strict(), .passthrough(), .default() as needed + * - Convert to discriminatedUnion for better performance + * + * ## Architecture + * + * Each transform is a named function operating on a ts-morph SourceFile. + * Transform arrays provide a declarative, reorderable pipeline. + * + * @see https://github.com/fabien0102/ts-to-zod + * @see https://github.com/dsherret/ts-morph + */ +import { execSync } from 'node:child_process'; +import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { generate } from 'ts-to-zod'; +import { + Project, + SyntaxKind, + Node, + CallExpression, + PropertyAssignment, + SourceFile, + InterfaceDeclaration, + TypeAliasDeclaration +} from 'ts-morph'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const PROJECT_ROOT = join(__dirname, '..'); + +const SPEC_TYPES_FILE = join(PROJECT_ROOT, 'src', 'spec.types.ts'); +const SDK_TYPES_FILE = join(PROJECT_ROOT, 'src', 'generated', 'sdk.types.ts'); +const GENERATED_DIR = join(PROJECT_ROOT, 'src', 'generated'); +const SCHEMA_OUTPUT_FILE = join(GENERATED_DIR, 'sdk.schemas.ts'); +const SCHEMA_TEST_OUTPUT_FILE = join(GENERATED_DIR, 'sdk.schemas.zod.test.ts'); +const GENERATE_SCRIPT_FILE = join(PROJECT_ROOT, 'scripts', 'generate-schemas.ts'); + +// Input files that trigger regeneration +const INPUT_FILES = [SPEC_TYPES_FILE, GENERATE_SCRIPT_FILE]; +// Output files that are generated +const OUTPUT_FILES = [SDK_TYPES_FILE, SCHEMA_OUTPUT_FILE, SCHEMA_TEST_OUTPUT_FILE]; + +/** + * Check if any input file is newer than any output file. + * Returns true if regeneration is needed. + */ +function needsRegeneration(): boolean { + // Get the newest input mtime + let newestInput = 0; + for (const file of INPUT_FILES) { + if (!existsSync(file)) { + console.log(` Input file missing: ${file}`); + return true; + } + const mtime = statSync(file).mtimeMs; + if (mtime > newestInput) newestInput = mtime; + } + + // Get the oldest output mtime + let oldestOutput = Infinity; + for (const file of OUTPUT_FILES) { + if (!existsSync(file)) { + console.log(` Output file missing: ${file}`); + return true; + } + const mtime = statSync(file).mtimeMs; + if (mtime < oldestOutput) oldestOutput = mtime; + } + + return newestInput > oldestOutput; +} + +// ============================================================================= +// Configuration: Field-level validation overrides +// ============================================================================= + +/** + * Base64 validation expression - validates that a string is valid base64. + * Used for image data, audio data, and blob contents. + */ +const BASE64_VALIDATOR = `z.string().refine( + (val) => { try { atob(val); return true; } catch { return false; } }, + { message: 'Invalid base64 string' } +)`; + +/** + * Field-level overrides for enhanced validation. + * These replace generated z.string() with more specific validators. + */ +const FIELD_OVERRIDES: Record> = { + AnnotationsSchema: { + lastModified: 'z.iso.datetime({ offset: true }).optional()' + }, + RootSchema: { + uri: 'z.string().startsWith("file://")' + }, + // Base64 validation for binary content + ImageContentSchema: { + data: BASE64_VALIDATOR + }, + AudioContentSchema: { + data: BASE64_VALIDATOR + }, + BlobResourceContentsSchema: { + blob: BASE64_VALIDATOR + } +}; + +/** + * Schemas that need .int() added to their z.number() members. + */ +const INTEGER_SCHEMAS = ['ProgressTokenSchema', 'RequestIdSchema']; + +/** + * Fields that need .default([]) for backwards compatibility. + * The SDK historically made these optional with empty array defaults. + */ +const ARRAY_DEFAULT_FIELDS: Record = { + CallToolResultSchema: ['content'], + ToolResultContentSchema: ['content'] +}; + +/** + * Union member ordering: ensure specific schemas match before general ones. + * More specific schemas (with more required fields) must come first in unions, + * otherwise Zod will match a simpler schema and strip extra fields. + * + * Example: { type: 'string', enum: [...], enumNames: [...] } should match + * LegacyTitledEnumSchema (which has enumNames) before UntitledSingleSelectEnumSchema. + */ +const UNION_MEMBER_ORDER: Record = { + // EnumSchema must come before StringSchema (both have type: 'string') + PrimitiveSchemaDefinitionSchema: ['EnumSchemaSchema', 'BooleanSchemaSchema', 'StringSchemaSchema', 'NumberSchemaSchema'], + // LegacyTitledEnumSchema must come first (has enumNames field) + EnumSchemaSchema: ['LegacyTitledEnumSchemaSchema', 'SingleSelectEnumSchemaSchema', 'MultiSelectEnumSchemaSchema'] +}; + +/** + * Schemas that need .strict() added for stricter validation. + */ +const STRICT_SCHEMAS = [ + 'JSONRPCRequestSchema', + 'JSONRPCNotificationSchema', + 'JSONRPCResultResponseSchema', + 'JSONRPCErrorResponseSchema', + 'EmptyResultSchema' +]; + +/** + * Schemas that should use z.discriminatedUnion instead of z.union for better performance. + * Maps schema name to the discriminator field name. + */ +const DISCRIMINATED_UNIONS: Record = { + SamplingContentSchema: 'type', + SamplingMessageContentBlockSchema: 'type', + ContentBlockSchema: 'type' +}; + +/** + * Derived capability types to add during pre-processing. + * These are extracted from parent capability interfaces for convenience. + * Format: { typeName: { parent: 'ParentInterface', property: 'propertyName' } } + */ +const DERIVED_CAPABILITY_TYPES: Record = { + ClientTasksCapability: { parent: 'ClientCapabilities', property: 'tasks' }, + ServerTasksCapability: { parent: 'ServerCapabilities', property: 'tasks' } + // Note: ElicitationCapability is kept local in types.ts because it has z.preprocess for backwards compat +}; + +// ============================================================================= +// Transform Infrastructure +// ============================================================================= + +/** A transform function that operates on a ts-morph SourceFile */ +type Transform = (sourceFile: SourceFile) => void; + + +/** + * Apply a list of transforms to content, logging each step. + */ +function applyTransforms(content: string, transforms: Transform[], label: string): string { + const project = new Project({ useInMemoryFileSystem: true }); + const sourceFile = project.createSourceFile('source.ts', content); + + console.log(` 🔧 ${label}...`); + for (const transform of transforms) { + console.log(` - ${transform.name}`); + transform(sourceFile); + } + + return sourceFile.getFullText(); +} + +// ============================================================================= +// Type Transforms (spec.types.ts → sdk.types.ts) +// ============================================================================= + +/** + * The SDK-specific meta key for relating messages to tasks. + * This is added to RequestParams._meta during pre-processing. + */ +const RELATED_TASK_META_KEY = 'io.modelcontextprotocol/related-task'; + +/** + * Abstract base types excluded from union discovery. + */ +const UNION_EXCLUSIONS = new Set([ + 'JSONRPCRequest', + 'JSONRPCNotification', + 'PaginatedRequest', + 'PaginatedResult' +]); + +// ============================================================================= +// Type Transforms (spec.types.ts → sdk.types.ts) +// ============================================================================= + +/** + * Type transforms applied to spec.types.ts before schema generation. + * These adapt the MCP spec types to SDK conventions. + * Order matters: transforms are applied in property definition order. + */ +const TypeTransforms = { + /** Transform `extends JSONRPCRequest` → `extends Request` */ + extendsJSONRPCRequest(sourceFile: SourceFile) { + for (const iface of sourceFile.getInterfaces()) { + for (const ext of iface.getExtends()) { + if (ext.getText() === 'JSONRPCRequest') { + ext.replaceWithText('Request'); + } + } + } + }, + + /** Transform `extends JSONRPCNotification` → `extends Notification` */ + extendsJSONRPCNotification(sourceFile: SourceFile) { + for (const iface of sourceFile.getInterfaces()) { + for (const ext of iface.getExtends()) { + if (ext.getText() === 'JSONRPCNotification') { + ext.replaceWithText('Notification'); + } + } + } + }, + + /** Inject RELATED_TASK_META_KEY into RequestParams._meta */ + injectRelatedTaskMetaKey: injectRelatedTaskMetaKey, + + /** Update Request.params and Notification.params types */ + updateRequestParamsType: updateRequestParamsType, + + /** Inline JSONRPCResponse into JSONRPCMessage union */ + inlineJSONRPCResponse: inlineJSONRPCResponse, + + /** Inject SDK-specific extensions (e.g., applyDefaults) */ + injectSdkExtensions: injectSdkExtensions, + + /** Convert JSDoc comments to @description for .describe() generation */ + convertJsDocToDescription: convertJsDocToDescription, + + /** Add derived capability types (ClientTasksCapability, etc.) */ + injectDerivedCapabilityTypes: injectDerivedCapabilityTypes, +}; + +/** + * Type cleanup transforms applied after main transforms. + * These prepare types for clean SDK export. + */ +const TypeCleanupTransforms = { + /** Remove standalone index signatures from interfaces */ + removeIndexSignatures(sourceFile: SourceFile) { + let count = 0; + for (const iface of sourceFile.getInterfaces()) { + const indexSigs = iface.getIndexSignatures(); + for (const sig of indexSigs) { + sig.remove(); + count++; + } + } + if (count > 0) { + console.log(` ✓ Removed ${count} standalone index signatures`); + } + }, + + /** Create union types (McpRequest, McpNotification, McpResult) */ + createUnionTypes: createUnionTypes, +}; + +// ============================================================================= +// Schema Transforms (generated Zod → final SDK schemas) +// ============================================================================= + +/** + * Schema transforms applied to ts-to-zod output. + * These adapt generated schemas for SDK conventions and Zod v4. + * Order matters: transforms are applied in property definition order. + */ +const SchemaTransforms = { + /** Transform z.record().and(z.object()) → z.object().passthrough() */ + recordAndToPassthrough: recordAndToPassthrough, + + /** Transform typeof expressions (z.any() → z.literal("2.0")) */ + typeofToLiteral: typeofToLiteral, + + /** Add .int() refinement to integer schemas */ + integerRefinements: integerRefinements, + + /** Add .default([]) to content arrays for backwards compat */ + arrayDefaults: arrayDefaults, + + /** Reorder union members (specific before general) */ + reorderUnionMembers: reorderUnionMembers, + + /** Convert z.union of literals to z.enum */ + unionToEnum: unionToEnum, + + /** Apply field-level validation overrides */ + fieldOverrides: fieldOverrides, + + /** Add .strict() to JSON-RPC schemas */ + strictSchemas: strictSchemas, + + /** Convert z.union to z.discriminatedUnion for tagged unions */ + discriminatedUnion: discriminatedUnion, + + /** Add .describe() to top-level schemas */ + topLevelDescribe: topLevelDescribe, + + /** Add AssertObjectSchema for capability schemas */ + assertObjectSchema: assertObjectSchema, + + /** Add z.preprocess for elicitation backwards compat */ + elicitationPreprocess: elicitationPreprocess, + + /** Convert capability schemas to looseObject */ + capabilitiesToLooseObject: capabilitiesToLooseObject, +}; + +// ============================================================================= +// Type Transform Implementations +// ============================================================================= + +/** + * Pre-process spec.types.ts using ts-morph to transform for SDK compatibility. + */ +function transformTypesForSdk(content: string): string { + return applyTransforms(content, Object.values(TypeTransforms), 'Transforming types for SDK'); +} + +/** + * Apply cleanup transforms to prepare types for export. + */ +function cleanupTypesForExport(content: string): string { + return applyTransforms(content, Object.values(TypeCleanupTransforms), 'Cleaning up types for export'); +} + + +/** + * Check if an interface transitively extends a base interface. + */ +function extendsBase( + iface: InterfaceDeclaration, + baseName: string, + sourceFile: SourceFile, + checked: Set = new Set() +): boolean { + const name = iface.getName(); + if (checked.has(name)) return false; + checked.add(name); + + for (const ext of iface.getExtends()) { + // Handle generic types like "Foo" -> "Foo" + const extName = ext.getText().split('<')[0].trim(); + if (extName === baseName) return true; + + const parent = sourceFile.getInterface(extName); + if (parent && extendsBase(parent, baseName, sourceFile, checked)) { + return true; + } + } + return false; +} + +/** + * Check if a type alias references a base type (e.g., `type EmptyResult = Result`). + */ +function referencesBase(alias: TypeAliasDeclaration, baseName: string): boolean { + const typeText = alias.getTypeNode()?.getText() || ''; + // Match patterns like "Result", "Result & Foo", "Foo & Result" + const pattern = new RegExp(`\\b${baseName}\\b`); + return pattern.test(typeText); +} + +/** + * Auto-discover union members by finding types that extend/reference a base type. + * + * Finds: + * - Interfaces that transitively extend the base (e.g., ListResourcesRequest → PaginatedRequest → Request) + * - Type aliases that reference the base (e.g., type EmptyResult = Result) + * + * Filters by naming convention (*Request, *Notification, *Result) and excludes abstract bases. + */ +function findUnionMembers( + sourceFile: SourceFile, + baseName: string, + exclusions: Set +): string[] { + const members: string[] = []; + + // Find interfaces that extend base (transitively) + for (const iface of sourceFile.getInterfaces()) { + const name = iface.getName(); + if (exclusions.has(name)) continue; + if (!name.endsWith(baseName)) continue; + if (extendsBase(iface, baseName, sourceFile)) { + members.push(name); + } + } + + // Find type aliases that reference base + for (const alias of sourceFile.getTypeAliases()) { + const name = alias.getName(); + if (exclusions.has(name)) continue; + if (!name.endsWith(baseName)) continue; + // Skip union types we're creating (McpRequest, etc.) + if (name.startsWith('Mcp')) continue; + // Skip Client/Server subsets + if (name.startsWith('Client') || name.startsWith('Server')) continue; + if (referencesBase(alias, baseName)) { + members.push(name); + } + } + + return members.sort(); +} + +/** + * Create union types (McpRequest, McpNotification, McpResult) from discovered members. + * + * Auto-discovers types that extend/reference base types and creates unions for type narrowing. + */ +function createUnionTypes(sourceFile: SourceFile): void { + const baseNames = ['Request', 'Notification', 'Result']; + + for (const baseName of baseNames) { + const baseInterface = sourceFile.getInterface(baseName); + if (!baseInterface) { + console.warn(` ⚠️ Interface ${baseName} not found`); + continue; + } + + // Auto-discover union members + const unionMembers = findUnionMembers(sourceFile, baseName, UNION_EXCLUSIONS); + + if (unionMembers.length === 0) { + console.warn(` ⚠️ No members found for ${baseName}`); + continue; + } + + // Create union type with Mcp prefix + const unionName = `Mcp${baseName}`; + const unionType = unionMembers.join('\n | '); + const insertPos = baseInterface.getEnd(); + sourceFile.insertText( + insertPos, + `\n\n/** Union of all MCP ${baseName.toLowerCase()} types for type narrowing. */\nexport type ${unionName} =\n | ${unionType};` + ); + + console.log(` ✓ Created ${unionName} with ${unionMembers.length} members`); + } +} + +/** + * Inject RELATED_TASK_META_KEY into RequestParams._meta interface. + * + * Before: + * _meta?: { + * progressToken?: ProgressToken; + * [key: string]: unknown; + * }; + * + * After: + * _meta?: { + * progressToken?: ProgressToken; + * 'io.modelcontextprotocol/related-task'?: RelatedTaskMetadata; + * [key: string]: unknown; + * }; + */ +function injectRelatedTaskMetaKey(sourceFile: SourceFile): void { + const requestParams = sourceFile.getInterface('RequestParams'); + if (!requestParams) { + console.warn(' ⚠️ RequestParams interface not found'); + return; + } + + const metaProp = requestParams.getProperty('_meta'); + if (!metaProp) { + console.warn(' ⚠️ _meta property not found in RequestParams'); + return; + } + + // Get the type of _meta (it's an inline type literal) + const typeNode = metaProp.getTypeNode(); + if (!typeNode || !Node.isTypeLiteral(typeNode)) { + console.warn(' ⚠️ _meta is not a type literal'); + return; + } + + // Check if already has the key + const existingMember = typeNode.getMembers().find(m => { + if (Node.isPropertySignature(m)) { + const name = m.getName(); + return name === `'${RELATED_TASK_META_KEY}'` || name === `"${RELATED_TASK_META_KEY}"`; + } + return false; + }); + + if (existingMember) { + console.log(' - RequestParams._meta already has RELATED_TASK_META_KEY'); + return; + } + + // Find the index signature ([key: string]: unknown) to insert before it + const members = typeNode.getMembers(); + const indexSignatureIndex = members.findIndex(m => Node.isIndexSignatureDeclaration(m)); + + // Create the new property + const newProperty = `/** + * If specified, this request is related to the provided task. + */ + '${RELATED_TASK_META_KEY}'?: RelatedTaskMetadata;`; + + if (indexSignatureIndex >= 0) { + // Insert before index signature + typeNode.insertMember(indexSignatureIndex, newProperty); + } else { + // Add at the end + typeNode.addMember(newProperty); + } + + console.log(' ✓ Injected RELATED_TASK_META_KEY into RequestParams._meta'); +} + +/** + * Update Request.params and Notification.params to use proper param types. + * + * Before: + * interface Request { params?: { [key: string]: any }; } + * interface Notification { params?: { [key: string]: any }; } + * + * After: + * interface Request { params?: RequestParams & { [key: string]: any }; } + * interface Notification { params?: NotificationParams & { [key: string]: any }; } + */ +function updateRequestParamsType(sourceFile: SourceFile): void { + // Update Request.params + const requestInterface = sourceFile.getInterface('Request'); + if (requestInterface) { + const paramsProp = requestInterface.getProperty('params'); + if (paramsProp) { + paramsProp.setType('RequestParams & { [key: string]: any }'); + console.log(' ✓ Updated Request.params to include RequestParams'); + } + } + + // Update Notification.params + const notificationInterface = sourceFile.getInterface('Notification'); + if (notificationInterface) { + const paramsProp = notificationInterface.getProperty('params'); + if (paramsProp) { + paramsProp.setType('NotificationParams & { [key: string]: any }'); + console.log(' ✓ Updated Notification.params to include NotificationParams'); + } + } +} + +/** + * Inline JSONRPCResponse into JSONRPCMessage and remove JSONRPCResponse type. + * This allows types.ts to define these as schema unions locally. + * + * Transforms: + * type JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse; + * type JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse; + * Into: + * type JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse; + * (JSONRPCResponse removed) + */ +function inlineJSONRPCResponse(sourceFile: SourceFile): void { + // Find and update JSONRPCMessage + const messageType = sourceFile.getTypeAlias('JSONRPCMessage'); + if (messageType) { + const typeNode = messageType.getTypeNode(); + if (typeNode) { + const text = typeNode.getText(); + // Replace JSONRPCResponse with its components + const newType = text.replace('JSONRPCResponse', 'JSONRPCResultResponse | JSONRPCErrorResponse'); + messageType.setType(newType); + console.log(' ✓ Inlined JSONRPCResponse into JSONRPCMessage'); + } + } + + // Remove JSONRPCResponse type alias + const responseType = sourceFile.getTypeAlias('JSONRPCResponse'); + if (responseType) { + responseType.remove(); + console.log(' ✓ Removed JSONRPCResponse type (defined locally in types.ts)'); + } +} + +/** + * Inject SDK-specific extensions to spec types. + * These are fields/types that the SDK adds beyond what the spec defines. + */ +function injectSdkExtensions(sourceFile: SourceFile): void { + // Add applyDefaults to ClientCapabilities.elicitation.form + // The SDK allows clients to request that servers apply schema defaults to elicitation responses + const clientCaps = sourceFile.getInterface('ClientCapabilities'); + if (clientCaps) { + const elicitationProp = clientCaps.getProperty('elicitation'); + if (elicitationProp) { + const typeNode = elicitationProp.getTypeNode(); + if (typeNode) { + const originalType = typeNode.getText(); + // Replace { form?: object; url?: object } with extended form type + if (originalType.includes('form?: object')) { + const newType = originalType.replace('form?: object', 'form?: { applyDefaults?: boolean; [key: string]: unknown }'); + typeNode.replaceWithText(newType); + console.log(' ✓ Added applyDefaults to ClientCapabilities.elicitation.form'); + } + } + } + } +} + +/** + * Add derived capability types by extracting nested properties from parent interfaces. + * This creates concrete interface definitions that ts-to-zod can generate schemas for. + * + * Example: ClientCapabilities.tasks becomes a standalone ClientTasksCapability interface. + */ +function injectDerivedCapabilityTypes(sourceFile: SourceFile): void { + for (const [typeName, { parent, property }] of Object.entries(DERIVED_CAPABILITY_TYPES)) { + // Check if already exists + if (sourceFile.getInterface(typeName) || sourceFile.getTypeAlias(typeName)) { + console.log(` - ${typeName} already exists`); + continue; + } + + // Find the parent interface + const parentInterface = sourceFile.getInterface(parent); + if (!parentInterface) { + console.warn(` ⚠️ Parent interface ${parent} not found for ${typeName}`); + continue; + } + + // Find the property + const prop = parentInterface.getProperty(property); + if (!prop) { + console.warn(` ⚠️ Property ${property} not found in ${parent} for ${typeName}`); + continue; + } + + // Get the type text and remove the optional marker if present + const typeNode = prop.getTypeNode(); + if (!typeNode) { + console.warn(` ⚠️ No type node for ${parent}.${property}`); + continue; + } + + let typeText = typeNode.getText(); + // Remove trailing '?' or '| undefined' to get the non-optional type + typeText = typeText.replace(/\s*\|\s*undefined\s*$/, '').trim(); + + // Get the JSDoc comment from the parent property for @description + const jsDocs = prop.getJsDocs(); + const description = jsDocs.length > 0 ? jsDocs[0].getDescription().trim() : ''; + + // Create the derived type alias with @description for .describe() generation + sourceFile.addTypeAlias({ + name: typeName, + isExported: true, + type: typeText, + docs: [description ? `@description ${description}` : `Extracted from ${parent}["${property}"].`] + }); + console.log(` ✓ Added derived type: ${typeName} from ${parent}.${property}`); + } +} + +/** + * Convert JSDoc comments to @description tags so ts-to-zod generates .describe() calls. + * + * Transforms comments like: + * /** The progress thus far. * / + * To: + * /** @description The progress thus far. * / + */ +function convertJsDocToDescription(sourceFile: SourceFile): void { + let count = 0; + + // Process all interfaces and their nested type literals + for (const iface of sourceFile.getInterfaces()) { + // Convert interface-level JSDoc + count += convertNodeJsDoc(iface); + + // Convert property-level JSDoc (including nested type literals) + count += processPropertiesRecursively(iface); + } + + // Process all type aliases + for (const typeAlias of sourceFile.getTypeAliases()) { + count += convertNodeJsDoc(typeAlias); + } + + console.log(` ✓ Converted ${count} JSDoc comments to @description`); +} + +/** + * Recursively process properties, including those in inline type literals. + */ +function processPropertiesRecursively(node: { getProperties?: () => Array; getTypeNode?: () => unknown }): number { + let count = 0; + + // Process direct properties + if (node.getProperties) { + for (const prop of node.getProperties() as Array<{ getJsDocs: () => unknown[]; getTypeNode?: () => unknown }>) { + count += convertNodeJsDoc(prop as Parameters[0]); + + // Check if the property has an inline type literal + if (prop.getTypeNode) { + const typeNode = prop.getTypeNode(); + if (typeNode && typeof typeNode === 'object' && 'getProperties' in typeNode) { + count += processPropertiesRecursively(typeNode as { getProperties: () => Array }); + } + } + } + } + + return count; +} + +/** + * Convert a node's JSDoc comment to use @description tag. + * Returns 1 if converted, 0 otherwise. + */ +function convertNodeJsDoc(node: { + getJsDocs: () => Array<{ + getDescription: () => string; + getTags: () => Array<{ getTagName: () => string }>; + replaceWithText: (text: string) => void; + }>; +}): number { + const jsDocs = node.getJsDocs(); + if (jsDocs.length === 0) return 0; + + const jsDoc = jsDocs[0]; + const description = jsDoc.getDescription().trim(); + + // Skip if no description or already has @description tag + if (!description) return 0; + if (jsDoc.getTags().some(tag => tag.getTagName() === 'description')) return 0; + + // Get existing tags to preserve them + const existingTags = jsDoc + .getTags() + .map(tag => { + const tagName = tag.getTagName(); + const tagText = tag + .getText() + .replace(new RegExp(`^@${tagName}\\s*`), '') + .trim(); + return `@${tagName}${tagText ? ' ' + tagText : ''}`; + }) + .join('\n * '); + + // Build new JSDoc with @description + const newJsDoc = existingTags ? `/**\n * @description ${description}\n * ${existingTags}\n */` : `/** @description ${description} */`; + + jsDoc.replaceWithText(newJsDoc); + return 1; +} + +// ============================================================================= +// Schema Transform Implementations +// ============================================================================= + +/** + * Transform generated schemas for SDK compatibility and Zod v4. + */ +function transformGeneratedSchemas(content: string): string { + // Text-based transforms (simple replacements) + content = content.replace('import { z } from "zod";', 'import { z } from "zod/v4";'); + content = content.replace( + '// Generated by ts-to-zod', + `// Generated by ts-to-zod +// Transformed for SDK compatibility (Zod v4, validation, discriminated unions, etc.) +// DO NOT EDIT - Run: npm run generate:schemas` + ); + + // Add .passthrough() to outputSchema + const outputSchemaPattern = /(outputSchema:\s*z\.object\(\{[\s\S]*?\}\))(\.optional\(\))/g; + if (outputSchemaPattern.test(content)) { + content = content.replace(outputSchemaPattern, '$1.passthrough()$2'); + console.log(' ✓ Added .passthrough() to ToolSchema.outputSchema'); + } + + // AST-based transforms + return applyTransforms(content, Object.values(SchemaTransforms), 'Transforming generated schemas'); +} + +/** + * Transform z.record(z.string(), z.unknown()).and(z.object({...})) to z.object({...}).passthrough() + * + * Using .passthrough() instead of looseObject because: + * - looseObject adds [x: string]: unknown index signature to the inferred type + * - This breaks TypeScript union narrowing (can't check 'prop' in obj) + * - .passthrough() allows extra properties at runtime without affecting the type + */ +function recordAndToPassthrough(sourceFile: SourceFile): void { + // Find all call expressions + sourceFile.forEachDescendant(node => { + if (!Node.isCallExpression(node)) return; + + const text = node.getText(); + // Match pattern: z.record(...).and(z.object({...})) + if (!text.startsWith('z.record(z.string(), z.unknown()).and(z.object(')) return; + + // Extract the object contents from z.object({...}) + const andCall = node; + const args = andCall.getArguments(); + if (args.length !== 1) return; + + const objectCall = args[0]; + if (!Node.isCallExpression(objectCall)) return; + + const objectArgs = objectCall.getArguments(); + if (objectArgs.length !== 1) return; + + const objectLiteral = objectArgs[0]; + if (!Node.isObjectLiteralExpression(objectLiteral)) return; + + // Replace with z.object({...}).passthrough() + // This allows extra properties at runtime but doesn't add index signature to type + const objectContent = objectLiteral.getText(); + node.replaceWithText(`z.object(${objectContent}).passthrough()`); + }); +} + +/** + * Transform typeof expressions that became z.any() into proper literals. + */ +function typeofToLiteral(sourceFile: SourceFile): void { + // Find property assignments with jsonrpc: z.any() + sourceFile.forEachDescendant(node => { + if (!Node.isPropertyAssignment(node)) return; + + const name = node.getName(); + const initializer = node.getInitializer(); + if (!initializer) return; + + const initText = initializer.getText(); + + if (name === 'jsonrpc' && initText === 'z.any()') { + node.setInitializer('z.literal("2.0")'); + } + }); +} + +/** + * Add .int() refinement to z.number() in specific schemas. + */ +function integerRefinements(sourceFile: SourceFile): void { + for (const schemaName of INTEGER_SCHEMAS) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + // Collect nodes first to avoid modifying while iterating + const nodesToReplace: CallExpression[] = []; + initializer.forEachDescendant(node => { + if (Node.isCallExpression(node) && node.getText() === 'z.number()') { + nodesToReplace.push(node); + } + }); + + // Replace in reverse order to maintain positions + for (const node of nodesToReplace.reverse()) { + node.replaceWithText('z.number().int()'); + } + } +} + +/** + * Add .default([]) to array fields for backwards compatibility. + * The SDK historically made certain content arrays optional with empty defaults. + */ +function arrayDefaults(sourceFile: SourceFile): void { + for (const [schemaName, fieldNames] of Object.entries(ARRAY_DEFAULT_FIELDS)) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + // Find property assignments for the target fields + initializer.forEachDescendant(node => { + if (!Node.isPropertyAssignment(node)) return; + + const propName = node.getName(); + if (!fieldNames.includes(propName)) return; + + // Get the initializer (the value assigned to the property) + const propInit = node.getInitializer(); + if (!propInit) return; + + const propText = propInit.getText(); + // Only add .default([]) if it's a z.array() and doesn't already have .default() + if (propText.includes('z.array(') && !propText.includes('.default(')) { + // Append .default([]) to the existing expression + propInit.replaceWithText(propText + '.default([])'); + } + }); + } +} + +/** + * Reorder union members according to UNION_MEMBER_ORDER configuration. + * This ensures more specific schemas are matched before general ones. + */ +function reorderUnionMembers(sourceFile: SourceFile): void { + for (const [schemaName, desiredOrder] of Object.entries(UNION_MEMBER_ORDER)) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + // Find the z.union([...]) call + let unionCall: CallExpression | undefined; + initializer.forEachDescendant(node => { + if (!Node.isCallExpression(node)) return; + const expr = node.getExpression(); + if (!Node.isPropertyAccessExpression(expr)) return; + if (expr.getName() === 'union') { + unionCall = node; + } + }); + + if (!unionCall) { + // Handle case where it's directly z.union(...) at top level + if (Node.isCallExpression(initializer)) { + const expr = initializer.getExpression(); + if (Node.isPropertyAccessExpression(expr) && expr.getName() === 'union') { + unionCall = initializer; + } + } + } + + if (!unionCall) continue; + + const args = unionCall.getArguments(); + if (args.length !== 1) continue; + + const arrayArg = args[0]; + if (!Node.isArrayLiteralExpression(arrayArg)) continue; + + // Get current member names + const elements = arrayArg.getElements(); + const currentMembers = elements.map(e => e.getText().trim()); + + // Check if reordering is needed + const orderedMembers = [...currentMembers].sort((a, b) => { + const aIdx = desiredOrder.indexOf(a); + const bIdx = desiredOrder.indexOf(b); + // If not in desiredOrder, keep at end + if (aIdx === -1 && bIdx === -1) return 0; + if (aIdx === -1) return 1; + if (bIdx === -1) return -1; + return aIdx - bIdx; + }); + + if (JSON.stringify(currentMembers) !== JSON.stringify(orderedMembers)) { + arrayArg.replaceWithText('[' + orderedMembers.join(', ') + ']'); + console.log(` ✓ Reordered ${schemaName} union members`); + } + } +} + +/** + * Transform z.union([z.literal('a'), z.literal('b'), ...]) to z.enum(['a', 'b', ...]) + * + * This handles cases that the regex approach missed, including chained methods. + */ +function unionToEnum(sourceFile: SourceFile): void { + // Collect union nodes that should be converted + const nodesToReplace: Array<{ node: CallExpression; values: string[] }> = []; + + sourceFile.forEachDescendant(node => { + if (!Node.isCallExpression(node)) return; + + // Check if this is z.union(...) + const expr = node.getExpression(); + if (!Node.isPropertyAccessExpression(expr)) return; + if (expr.getName() !== 'union') return; + + const args = node.getArguments(); + if (args.length !== 1) return; + + const arrayArg = args[0]; + if (!Node.isArrayLiteralExpression(arrayArg)) return; + + // Check if ALL elements are z.literal('string') + const elements = arrayArg.getElements(); + if (elements.length < 2) return; + + const literalValues: string[] = []; + let allStringLiterals = true; + + for (const element of elements) { + if (!Node.isCallExpression(element)) { + allStringLiterals = false; + break; + } + + const elemExpr = element.getExpression(); + if (!Node.isPropertyAccessExpression(elemExpr)) { + allStringLiterals = false; + break; + } + + if (elemExpr.getName() !== 'literal') { + allStringLiterals = false; + break; + } + + const elemArgs = element.getArguments(); + if (elemArgs.length !== 1) { + allStringLiterals = false; + break; + } + + const literalArg = elemArgs[0]; + if (!Node.isStringLiteral(literalArg)) { + allStringLiterals = false; + break; + } + + literalValues.push(literalArg.getLiteralValue()); + } + + if (allStringLiterals && literalValues.length >= 2) { + nodesToReplace.push({ node, values: literalValues }); + } + }); + + // Replace in reverse order + for (const { node, values } of nodesToReplace.reverse()) { + const enumValues = values.map(v => `'${v}'`).join(', '); + node.replaceWithText(`z.enum([${enumValues}])`); + } +} + +/** + * Apply field-level validation overrides to specific schemas. + */ +function fieldOverrides(sourceFile: SourceFile): void { + for (const [schemaName, fields] of Object.entries(FIELD_OVERRIDES)) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) { + console.warn(` ⚠️ Schema not found for override: ${schemaName}`); + continue; + } + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + // Find property assignments matching the field names + initializer.forEachDescendant(node => { + if (!Node.isPropertyAssignment(node)) return; + + const propName = node.getName(); + if (fields[propName]) { + console.log(` ✓ Override: ${schemaName}.${propName}`); + node.setInitializer(fields[propName]); + } + }); + } +} + +/** + * Add .strict() to specified schemas for stricter validation. + * This matches the SDK's behavior of rejecting unknown properties. + */ +function strictSchemas(sourceFile: SourceFile): void { + for (const schemaName of STRICT_SCHEMAS) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + // Append .strict() to the schema + const currentText = initializer.getText(); + varDecl.setInitializer(`${currentText}.strict()`); + console.log(` ✓ Added .strict() to ${schemaName}`); + } +} + +/** + * Convert z.union() to z.discriminatedUnion() for specified schemas. + * This provides better performance and error messages for tagged unions. + */ +function discriminatedUnion(sourceFile: SourceFile): void { + for (const [schemaName, discriminator] of Object.entries(DISCRIMINATED_UNIONS)) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + const text = initializer.getText(); + + // Match z.union([...]) pattern and convert to z.discriminatedUnion('discriminator', [...]) + const unionMatch = text.match(/^z\.union\(\s*\[([\s\S]*)\]\s*\)$/); + if (unionMatch) { + const members = unionMatch[1]; + varDecl.setInitializer(`z.discriminatedUnion('${discriminator}', [${members}])`); + console.log(` ✓ Converted ${schemaName} to discriminatedUnion('${discriminator}')`); + } + } +} + +/** + * Add .describe() to top-level schemas based on their JSDoc @description tag. + * ts-to-zod only adds .describe() to properties, not to the schema itself. + */ +function topLevelDescribe(sourceFile: SourceFile): void { + let count = 0; + + for (const varStmt of sourceFile.getVariableStatements()) { + // Get JSDoc from the variable statement + const jsDocs = varStmt.getJsDocs(); + if (jsDocs.length === 0) continue; + + const jsDoc = jsDocs[0]; + const descTag = jsDoc.getTags().find(tag => tag.getTagName() === 'description'); + if (!descTag) continue; + + // Get the description text + const descText = descTag.getCommentText()?.trim(); + if (!descText) continue; + + // Get the variable declaration + const decl = varStmt.getDeclarations()[0]; + if (!decl) continue; + + const schemaName = decl.getName(); + if (!schemaName.endsWith('Schema')) continue; + + const initializer = decl.getInitializer(); + if (!initializer) continue; + + const currentText = initializer.getText(); + + // Skip if already has .describe() at the end + if (/\.describe\([^)]+\)\s*$/.test(currentText)) continue; + + // Escape backslashes first, then quotes and newlines + const escapedDesc = descText.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, ' '); + + // Add .describe() to the schema + decl.setInitializer(`${currentText}.describe('${escapedDesc}')`); + count++; + } + + if (count > 0) { + console.log(` ✓ Added .describe() to ${count} top-level schemas`); + } +} + +/** + * Schemas where z.record(z.string(), z.any()) should be replaced with AssertObjectSchema. + * These are capability schemas that use `object` type for extensibility. + */ +const ASSERT_OBJECT_SCHEMAS = [ + 'ClientCapabilitiesSchema', + 'ServerCapabilitiesSchema', + 'ClientTasksCapabilitySchema', + 'ServerTasksCapabilitySchema' +]; + +/** + * Add AssertObjectSchema definition and replace z.record(z.string(), z.any()) with it + * in capability schemas. This provides better TypeScript typing (object vs { [x: string]: any }). + */ +function assertObjectSchema(sourceFile: SourceFile): void { + // Check if any of the target schemas exist + const hasTargetSchemas = ASSERT_OBJECT_SCHEMAS.some(name => sourceFile.getVariableDeclaration(name)); + if (!hasTargetSchemas) return; + + // Add AssertObjectSchema definition after imports + const lastImport = sourceFile.getImportDeclarations().at(-1); + if (lastImport) { + lastImport.replaceWithText(`${lastImport.getText()} + +/** + * Assert 'object' type schema - validates that value is a non-null object. + * Provides better TypeScript typing than z.record(z.string(), z.any()). + * @internal + */ +const AssertObjectSchema = z.custom((v): v is object => v !== null && (typeof v === 'object' || typeof v === 'function'));`); + } + + // Replace z.record(z.string(), z.any()) with AssertObjectSchema in target schemas + let count = 0; + for (const schemaName of ASSERT_OBJECT_SCHEMAS) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + const text = initializer.getText(); + // Replace the pattern - note we need to handle optional() suffix too + const newText = text.replace(/z\.record\(z\.string\(\), z\.any\(\)\)/g, 'AssertObjectSchema'); + + if (newText !== text) { + varDecl.setInitializer(newText); + count++; + } + } + + if (count > 0) { + console.log(` ✓ Replaced z.record(z.string(), z.any()) with AssertObjectSchema in ${count} schemas`); + } +} + +/** + * Convert capability schemas to use looseObject for extensibility. + * The spec says capabilities are "not a closed set" - any client/server can define + * additional capabilities. Using looseObject allows extra properties. + */ +function capabilitiesToLooseObject(sourceFile: SourceFile): void { + const CAPABILITY_SCHEMAS = [ + 'ClientCapabilitiesSchema', + 'ServerCapabilitiesSchema', + 'ClientTasksCapabilitySchema', + 'ServerTasksCapabilitySchema' + ]; + + let count = 0; + for (const schemaName of CAPABILITY_SCHEMAS) { + const varDecl = sourceFile.getVariableDeclaration(schemaName); + if (!varDecl) continue; + + const initializer = varDecl.getInitializer(); + if (!initializer) continue; + + const text = initializer.getText(); + // Replace z.object( with z.looseObject( for nested objects in capabilities + // This allows extensibility for additional capability properties + const newText = text.replace(/z\.object\(/g, 'z.looseObject('); + + if (newText !== text) { + varDecl.setInitializer(newText); + count++; + } + } + + if (count > 0) { + console.log(` ✓ Converted ${count} capability schemas to use looseObject for extensibility`); + } +} + +/** + * Add z.preprocess to ClientCapabilitiesSchema.elicitation for backwards compatibility. + * - preprocess: transforms empty {} to { form: {} } for SDK backwards compatibility + * - keeps original schema structure to maintain type compatibility with spec + */ +function elicitationPreprocess(sourceFile: SourceFile): void { + const varDecl = sourceFile.getVariableDeclaration('ClientCapabilitiesSchema'); + if (!varDecl) return; + + const initializer = varDecl.getInitializer(); + if (!initializer) return; + + let text = initializer.getText(); + + // Find the elicitation field and wrap with preprocess + // Handle both z.object and z.looseObject patterns + // The inner schema structure may be complex (nested objects, etc.), so we use brace counting + const elicitationStart = text.indexOf('elicitation:'); + if (elicitationStart === -1) return; + + // Find the schema after 'elicitation:' + const afterElicitation = text.substring(elicitationStart + 'elicitation:'.length); + + // Find where z.object or z.looseObject starts + const objectMatch = afterElicitation.match(/^\s*(z\s*\.\s*(?:looseObject|object)\s*\()/); + if (!objectMatch) return; + + const schemaStart = elicitationStart + 'elicitation:'.length + objectMatch.index!; + + // Count braces/parens to find the end of the schema (before .optional()) + let depth = 0; + let inString = false; + let stringChar = ''; + let schemaEnd = schemaStart; + const startPos = schemaStart; + + for (let i = startPos; i < text.length; i++) { + const char = text[i]; + const prevChar = i > 0 ? text[i - 1] : ''; + + if (inString) { + if (char === stringChar && prevChar !== '\\') { + inString = false; + } + } else { + if (char === '"' || char === "'") { + inString = true; + stringChar = char; + } else if (char === '(' || char === '{' || char === '[') { + depth++; + } else if (char === ')' || char === '}' || char === ']') { + depth--; + if (depth === 0) { + schemaEnd = i + 1; + break; + } + } + } + } + + // Extract the inner schema (z.looseObject({...}) or z.object({...})) + const innerSchema = text.substring(schemaStart, schemaEnd).trim(); + + // Find what follows the schema (.optional().describe(...) etc) + const afterSchema = text.substring(schemaEnd); + const optionalMatch = afterSchema.match(/^\s*\.optional\(\)(\s*\.describe\([^)]*\))?/); + if (!optionalMatch) return; + + const describeCall = optionalMatch[1] || ''; + const fullMatchEnd = schemaEnd + optionalMatch[0].length; + + // Build the replacement with preprocess + const replacement = `elicitation: z.preprocess( + value => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (Object.keys(value as Record).length === 0) { + return { form: {} }; + } + } + return value; + }, + ${innerSchema}${describeCall} + ).optional()`; + + text = text.substring(0, elicitationStart) + replacement + text.substring(fullMatchEnd); + varDecl.setInitializer(text); + console.log(' ✓ Added z.preprocess to ClientCapabilitiesSchema.elicitation'); +} + +// ============================================================================= +// Main +// ============================================================================= + +async function main() { + const ifChanged = process.argv.includes('--if-changed'); + + if (ifChanged) { + if (!needsRegeneration()) { + console.log('✅ Schemas are up to date, skipping generation.'); + return; + } + console.log('🔄 Input files changed, regenerating schemas...\n'); + } else { + console.log('🔧 Generating Zod schemas from spec.types.ts...\n'); + } + + // Ensure generated directory exists + if (!existsSync(GENERATED_DIR)) { + mkdirSync(GENERATED_DIR, { recursive: true }); + } + + // Phase 1: Transform types for SDK + const rawSourceText = readFileSync(SPEC_TYPES_FILE, 'utf-8'); + const sdkTypesContent = transformTypesForSdk(rawSourceText); + const cleanedTypesContent = cleanupTypesForExport(sdkTypesContent); + + // Write types with header + const sdkTypesWithHeader = `/* eslint-disable @typescript-eslint/no-empty-object-type */ +/** + * SDK-compatible types generated from spec.types.ts + * + * This file is auto-generated by scripts/generate-schemas.ts + * DO NOT EDIT MANUALLY + * + * Transformations applied: + * - \`extends JSONRPCRequest\` → \`extends Request\` + * - \`extends JSONRPCNotification\` → \`extends Notification\` + * - All index signature patterns removed (enables TypeScript union narrowing) + * + * Note: Schemas use .passthrough() for runtime extensibility, so types + * don't need index signatures. This separation allows clean types for + * TypeScript while maintaining runtime flexibility. + */ +${cleanedTypesContent.replace(/^\/\*\*[\s\S]*?\*\/\n/, '')}`; + writeFileSync(SDK_TYPES_FILE, sdkTypesWithHeader, 'utf-8'); + console.log(`✅ Written: ${SDK_TYPES_FILE}`); + + const result = generate({ + sourceText: sdkTypesContent, + keepComments: true, + skipParseJSDoc: false, + // Use PascalCase naming to match existing types.ts convention + getSchemaName: (typeName: string) => `${typeName}Schema` + }); + + if (result.errors.length > 0) { + console.error('❌ Generation errors:'); + for (const error of result.errors) { + console.error(` - ${error}`); + } + process.exit(1); + } + + if (result.hasCircularDependencies) { + console.warn('⚠️ Warning: Circular dependencies detected in types'); + } + + // Phase 2: Transform generated schemas + let schemasContent = result.getZodSchemasFile('./sdk.types.js'); + schemasContent = transformGeneratedSchemas(schemasContent); + + writeFileSync(SCHEMA_OUTPUT_FILE, schemasContent, 'utf-8'); + console.log(`✅ Written: ${SCHEMA_OUTPUT_FILE}`); + + // Generate integration tests that verify schemas match TypeScript types + const testsContent = result.getIntegrationTestFile('./sdk.types.js', './sdk.schemas.js'); + if (testsContent) { + const processedTests = postProcessTests(testsContent); + writeFileSync(SCHEMA_TEST_OUTPUT_FILE, processedTests, 'utf-8'); + console.log(`✅ Written: ${SCHEMA_TEST_OUTPUT_FILE}`); + } + + // Format generated files with prettier + console.log('\n📝 Formatting generated files...'); + execSync('npx prettier --write "src/generated/**/*"', { + cwd: PROJECT_ROOT, + stdio: 'inherit' + }); + + console.log('\n🎉 Schema generation complete!'); +} + +/** + * Post-process generated integration tests. + */ +function postProcessTests(content: string): string { + content = content.replace('import { z } from "zod";', 'import { z } from "zod/v4";'); + + content = content.replace( + '// Generated by ts-to-zod', + `// Generated by ts-to-zod +// Integration tests verifying schemas match TypeScript types +// Run: npm run generate:schemas` + ); + + // Comment out bidirectional type checks for schemas that use looseObject or passthrough. + // These add index signatures [x: string]: unknown to schema-inferred types, but + // we've removed index signatures from spec types (for union narrowing). + // The one-way check (schema-inferred → spec) is kept to ensure compatibility. + const schemasWithIndexSignatures = [ + // Capability schemas use looseObject + 'ClientCapabilities', + 'ServerCapabilities', + 'ClientTasksCapability', + 'ServerTasksCapability', + 'InitializeResult', + 'InitializeRequestParams', + 'InitializeRequest', + 'ClientRequest', // Contains InitializeRequest + // Result-based schemas use passthrough (Result extends removed index sig) + 'Result', + 'EmptyResult', + 'PaginatedResult', + 'JSONRPCResultResponse', + 'CreateTaskResult', + 'GetTaskResult', + 'CancelTaskResult', + 'ListTasksResult', + 'CompleteResult', + 'ElicitResult', + 'ListRootsResult', + 'ReadResourceResult', + 'ListToolsResult', + 'ListPromptsResult', + 'ListResourceTemplatesResult', + 'ListResourcesResult', + 'CallToolResult', + 'GetPromptResult', + 'CreateMessageResult', + 'GetTaskPayloadResult', // Has explicit Record extension + // Request/Notification based schemas also use passthrough + 'Request', + 'Notification', + 'RequestParams', + 'NotificationParams', + // Union types that include passthrough schemas + 'JSONRPCMessage' + ]; + + let commentedCount = 0; + for (const schemaName of schemasWithIndexSignatures) { + // Comment out spec → schema-inferred checks (these fail with passthrough/looseObject) + // ts-to-zod generates PascalCase type names + // Pattern matches: expectType({} as spec.Foo) + const pattern = new RegExp(`(expectType<${schemaName}SchemaInferredType>\\(\\{\\} as spec\\.${schemaName}\\))`, 'g'); + const before = content; + content = content.replace( + pattern, + `// Skip: passthrough/looseObject index signature incompatible with clean spec interface\n// $1` + ); + if (before !== content) { + commentedCount++; + } + } + if (commentedCount > 0) { + console.log(` ✓ Commented out ${commentedCount} index-signature type checks in test file`); + } + + // Union types: Request, Notification, Result are now union types, so schema-inferred + // (which is object type) can't be assigned to them. Comment out both directions. + const unionTypes = ['Request', 'Notification', 'Result']; + let unionCommentedCount = 0; + for (const typeName of unionTypes) { + // Comment out schema-inferred → spec checks (schema object can't satisfy union) + const specPattern = new RegExp(`(expectType\\(\\{\\} as ${typeName}SchemaInferredType\\))`, 'g'); + const before = content; + content = content.replace(specPattern, `// Skip: schema-inferred object type incompatible with spec union type\n// $1`); + if (before !== content) { + unionCommentedCount++; + } + } + if (unionCommentedCount > 0) { + console.log(` ✓ Commented out ${unionCommentedCount} union type checks in test file`); + } + + return content; +} + +main().catch(error => { + console.error('❌ Schema generation failed:', error); + process.exit(1); +}); diff --git a/src/client/index.ts b/src/client/index.ts index 28c0e6253..63f36b0b2 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -48,7 +48,10 @@ import { type ListChangedHandlers, type Request, type Notification, - type Result + type Result, + type McpRequest, + type McpNotification, + type McpResult } from '../types.js'; import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js'; import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator } from '../validation/types.js'; @@ -231,9 +234,9 @@ export type ClientOptions = ProtocolOptions & { * ``` */ export class Client< - RequestT extends Request = Request, - NotificationT extends Notification = Notification, - ResultT extends Result = Result + RequestT extends Request = McpRequest, + NotificationT extends Notification = McpNotification, + ResultT extends Result = McpResult > extends Protocol { private _serverCapabilities?: ServerCapabilities; private _serverVersion?: Implementation; @@ -407,7 +410,8 @@ export class Client< const requestedSchema = params.mode === 'form' ? (params.requestedSchema as JsonSchemaType) : undefined; if (params.mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) { - if (this._capabilities.elicitation?.form?.applyDefaults) { + // applyDefaults is an SDK extension not in the spec, so cast to access it + if ((this._capabilities.elicitation?.form as { applyDefaults?: boolean } | undefined)?.applyDefaults) { try { applyElicitationDefaults(requestedSchema, validatedResult.content); } catch { diff --git a/src/examples/client/parallelToolCallsClient.ts b/src/examples/client/parallelToolCallsClient.ts index 2ad249de7..7513ef3dc 100644 --- a/src/examples/client/parallelToolCallsClient.ts +++ b/src/examples/client/parallelToolCallsClient.ts @@ -118,7 +118,7 @@ async function startParallelNotificationTools(client: Client): Promise { } if (inputCancelled) { - return { action: 'cancel' }; + return { action: 'cancel' as const }; } // If we didn't complete all fields due to an error, try again @@ -394,7 +394,7 @@ async function connect(url?: string): Promise { continue; } else { console.log('Maximum attempts reached. Declining request.'); - return { action: 'decline' }; + return { action: 'decline' as const }; } } @@ -412,7 +412,7 @@ async function connect(url?: string): Promise { continue; } else { console.log('Maximum attempts reached. Declining request.'); - return { action: 'decline' }; + return { action: 'decline' as const }; } } @@ -428,23 +428,23 @@ async function connect(url?: string): Promise { if (confirmAnswer === 'yes' || confirmAnswer === 'y') { return { - action: 'accept', - content + action: 'accept' as const, + content: content as { [key: string]: string | number | boolean | string[] } }; } else if (confirmAnswer === 'cancel' || confirmAnswer === 'c') { - return { action: 'cancel' }; + return { action: 'cancel' as const }; } else if (confirmAnswer === 'no' || confirmAnswer === 'n') { if (attempts < maxAttempts) { console.log('Please re-enter the information...'); continue; } else { - return { action: 'decline' }; + return { action: 'decline' as const }; } } } console.log('Maximum attempts reached. Declining request.'); - return { action: 'decline' }; + return { action: 'decline' as const }; }); transport = new StreamableHTTPClientTransport(new URL(serverUrl), { diff --git a/src/examples/client/simpleTaskInteractiveClient.ts b/src/examples/client/simpleTaskInteractiveClient.ts index 06ed0ead1..5f7432365 100644 --- a/src/examples/client/simpleTaskInteractiveClient.ts +++ b/src/examples/client/simpleTaskInteractiveClient.ts @@ -14,6 +14,7 @@ import { CallToolResultSchema, TextContent, ElicitRequestSchema, + ElicitResult, CreateMessageRequestSchema, CreateMessageRequest, CreateMessageResult, @@ -40,11 +41,7 @@ function getTextContent(result: { content: Array<{ type: string; text?: string } return textContent?.text ?? '(no text)'; } -async function elicitationCallback(params: { - mode?: string; - message: string; - requestedSchema?: object; -}): Promise<{ action: string; content?: Record }> { +async function elicitationCallback(params: { mode?: string; message: string; requestedSchema?: object }): Promise { console.log(`\n[Elicitation] Server asks: ${params.message}`); // Simple terminal prompt for y/n @@ -52,7 +49,7 @@ async function elicitationCallback(params: { const confirmed = ['y', 'yes', 'true', '1'].includes(response.toLowerCase()); console.log(`[Elicitation] Responding with: confirm=${confirmed}`); - return { action: 'accept', content: { confirm: confirmed } }; + return { action: 'accept' as const, content: { confirm: confirmed } }; } async function samplingCallback(params: CreateMessageRequest['params']): Promise { diff --git a/src/examples/server/simpleStreamableHttp.ts b/src/examples/server/simpleStreamableHttp.ts index e3b754fa6..843370d64 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -488,7 +488,7 @@ const getServer = () => { text: `Completed ${duration}ms delay` } ] - }); + } as CallToolResult); })(); // Return CreateTaskResult with the created task diff --git a/src/examples/server/simpleTaskInteractive.ts b/src/examples/server/simpleTaskInteractive.ts index db0a4b579..655585d27 100644 --- a/src/examples/server/simpleTaskInteractive.ts +++ b/src/examples/server/simpleTaskInteractive.ts @@ -561,7 +561,7 @@ const createServer = (): Server => { console.log(`[Server] Completing task with result: ${text}`); await taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text }] - }); + } as CallToolResult); } else if (name === 'write_haiku') { const topic = args?.topic ?? 'nature'; console.log(`[Server] write_haiku: topic '${topic}'`); @@ -586,14 +586,14 @@ const createServer = (): Server => { console.log('[Server] Completing task with haiku'); await taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Haiku:\n${haiku}` }] - }); + } as CallToolResult); } } catch (error) { console.error(`[Server] Task ${task.taskId} failed:`, error); await taskStore.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: `Error: ${error}` }], isError: true - }); + } as CallToolResult); } finally { activeTaskExecutions.delete(task.taskId); } @@ -622,7 +622,8 @@ const createServer = (): Server => { server.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra): Promise => { const { taskId } = request.params; console.log(`[Server] tasks/result called for task ${taskId}`); - return taskResultHandler.handle(taskId, server, extra.sessionId ?? ''); + const result = await taskResultHandler.handle(taskId, server, extra.sessionId ?? ''); + return result as GetTaskPayloadResult; }); return server; diff --git a/src/examples/server/toolWithSampleServer.ts b/src/examples/server/toolWithSampleServer.ts index e6d733598..b46dac3ea 100644 --- a/src/examples/server/toolWithSampleServer.ts +++ b/src/examples/server/toolWithSampleServer.ts @@ -33,12 +33,13 @@ mcpServer.registerTool( maxTokens: 500 }); - // Since we're not passing tools param to createMessage, response.content is single content + // Extract text from response content (could be single block or array) + const contentBlock = Array.isArray(response.content) ? response.content[0] : response.content; return { content: [ { type: 'text', - text: response.content.type === 'text' ? response.content.text : 'Unable to generate summary' + text: contentBlock?.type === 'text' ? contentBlock.text : 'Unable to generate summary' } ] }; diff --git a/src/generated/sdk.schemas.ts b/src/generated/sdk.schemas.ts new file mode 100644 index 000000000..eedffe6fb --- /dev/null +++ b/src/generated/sdk.schemas.ts @@ -0,0 +1,2640 @@ +// Generated by ts-to-zod +// Transformed for SDK compatibility (Zod v4, validation, discriminated unions, etc.) +// DO NOT EDIT - Run: npm run generate:schemas +import { z } from 'zod/v4'; + +/** + * Assert 'object' type schema - validates that value is a non-null object. + * Provides better TypeScript typing than z.record(z.string(), z.any()). + * @internal + */ +const AssertObjectSchema = z.custom((v): v is object => v !== null && (typeof v === 'object' || typeof v === 'function')); + +/** + * @description A progress token, used to associate progress notifications with the original request. + * @category Common Types + */ +export const ProgressTokenSchema = z + .union([z.string(), z.number().int()]) + .describe('A progress token, used to associate progress notifications with the original request.'); + +/** + * @description An opaque token used to represent a cursor for pagination. + * @category Common Types + */ +export const CursorSchema = z.string().describe('An opaque token used to represent a cursor for pagination.'); + +/** + * @description Metadata for augmenting a request with task execution. +Include this in the `task` field of the request parameters. + * @category `tasks` + */ +export const TaskMetadataSchema = z + .object({ + /** @description Requested duration in milliseconds to retain task from creation. */ + ttl: z.number().optional().describe('Requested duration in milliseconds to retain task from creation.') + }) + .describe('Metadata for augmenting a request with task execution. Include this in the `task` field of the request parameters.'); + +/** + * @description Metadata for associating messages with a task. +Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + * @category `tasks` + */ +export const RelatedTaskMetadataSchema = z + .object({ + /** @description The task identifier this message is associated with. */ + taskId: z.string().describe('The task identifier this message is associated with.') + }) + .describe( + 'Metadata for associating messages with a task. Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.' + ); + +/** + * @description Common params for any request. + * @internal + */ +export const RequestParamsSchema = z + .object({ + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .object({ + /** @description If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. */ + progressToken: ProgressTokenSchema.optional().describe( + 'If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.' + ), + /** @description If specified, this request is related to the provided task. */ + 'io.modelcontextprotocol/related-task': RelatedTaskMetadataSchema.optional().describe( + 'If specified, this request is related to the provided task.' + ) + }) + .passthrough() + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Common params for any request.'); + +/** @internal */ +export const NotificationParamsSchema = z.object({ + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') +}); + +/** @internal */ +export const NotificationSchema = z.object({ + method: z.string(), + // Allow unofficial extensions of `Notification.params` without impacting `NotificationParams`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: NotificationParamsSchema.and(z.record(z.string(), z.any())).optional() +}); + +/** + * @category Common Types + */ +export const ResultSchema = z + .object({ + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .passthrough(); + +/** + * @category Common Types + */ +export const ErrorSchema = z.object({ + /** @description The error type that occurred. */ + code: z.number().describe('The error type that occurred.'), + /** @description A short description of the error. The message SHOULD be limited to a concise single sentence. */ + message: z.string().describe('A short description of the error. The message SHOULD be limited to a concise single sentence.'), + /** @description Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). */ + data: z + .unknown() + .optional() + .describe( + 'Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).' + ) +}); + +/** + * @description A uniquely identifying ID for a request in JSON-RPC. + * @category Common Types + */ +export const RequestIdSchema = z.union([z.string(), z.number().int()]).describe('A uniquely identifying ID for a request in JSON-RPC.'); + +/** @internal */ +export const RequestSchema = z.object({ + method: z.string(), + // Allow unofficial extensions of `Request.params` without impacting `RequestParams`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: RequestParamsSchema.and(z.record(z.string(), z.any())).optional() +}); + +/** + * @description A notification which does not expect a response. + * @category JSON-RPC + */ +export const JSONRPCNotificationSchema = NotificationSchema.extend({ + jsonrpc: z.literal('2.0') +}) + .strict() + .describe('A notification which does not expect a response.'); + +/** + * @description A successful (non-error) response to a request. + * @category JSON-RPC + */ +export const JSONRPCResultResponseSchema = z + .object({ + jsonrpc: z.literal('2.0'), + id: RequestIdSchema, + result: ResultSchema + }) + .strict() + .describe('A successful (non-error) response to a request.'); + +/** + * @description A response to a request that indicates an error occurred. + * @category JSON-RPC + */ +export const JSONRPCErrorResponseSchema = z + .object({ + jsonrpc: z.literal('2.0'), + id: RequestIdSchema.optional(), + error: ErrorSchema + }) + .strict() + .describe('A response to a request that indicates an error occurred.'); + +/* Empty result */ +/** + * @description A response that indicates success but carries no data. + * @category Common Types + */ +export const EmptyResultSchema = ResultSchema.describe('A response that indicates success but carries no data.') + .strict() + .describe('A response that indicates success but carries no data.'); + +/* Cancellation */ +/** + * @description Parameters for a `notifications/cancelled` notification. + * @category `notifications/cancelled` + */ +export const CancelledNotificationParamsSchema = NotificationParamsSchema.extend({ + /** @description The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). */ + requestId: RequestIdSchema.optional().describe( + 'The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction.\nThis MUST be provided for cancelling non-task requests.\nThis MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead).' + ), + /** @description An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. */ + reason: z + .string() + .optional() + .describe('An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.') +}).describe('Parameters for a `notifications/cancelled` notification.'); + +/** + * @description This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + +The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + +This notification indicates that the result will be unused, so any associated processing SHOULD cease. + +A client MUST NOT attempt to cancel its `initialize` request. + +For task cancellation, use the `tasks/cancel` request instead of this notification. + * @category `notifications/cancelled` + */ +export const CancelledNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/cancelled'), + params: CancelledNotificationParamsSchema +}).describe( + 'This notification can be sent by either side to indicate that it is cancelling a previously-issued request. The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. This notification indicates that the result will be unused, so any associated processing SHOULD cease. A client MUST NOT attempt to cancel its `initialize` request. For task cancellation, use the `tasks/cancel` request instead of this notification.' +); + +/** + * @description Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. + * @category `initialize` + */ +export const ClientCapabilitiesSchema = z + .looseObject({ + /** @description Experimental, non-standard capabilities that the client supports. */ + experimental: z + .record(z.string(), AssertObjectSchema) + .optional() + .describe('Experimental, non-standard capabilities that the client supports.'), + /** @description Present if the client supports listing roots. */ + roots: z + .looseObject({ + /** @description Whether the client supports notifications for changes to the roots list. */ + listChanged: z.boolean().optional().describe('Whether the client supports notifications for changes to the roots list.') + }) + .optional() + .describe('Present if the client supports listing roots.'), + /** @description Present if the client supports sampling from an LLM. */ + sampling: z + .looseObject({ + /** @description Whether the client supports context inclusion via includeContext parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). */ + context: AssertObjectSchema.optional().describe( + 'Whether the client supports context inclusion via includeContext parameter.\nIf not declared, servers SHOULD only use `includeContext: "none"` (or omit it).' + ), + /** @description Whether the client supports tool use via tools and toolChoice parameters. */ + tools: AssertObjectSchema.optional().describe('Whether the client supports tool use via tools and toolChoice parameters.') + }) + .optional() + .describe('Present if the client supports sampling from an LLM.'), + /** @description Present if the client supports elicitation from the server. */ + elicitation: z + .preprocess( + value => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (Object.keys(value as Record).length === 0) { + return { form: {} }; + } + } + return value; + }, + z + .looseObject({ + form: z + .looseObject({ + applyDefaults: z.boolean().optional() + }) + .passthrough() + .optional(), + url: AssertObjectSchema.optional() + }) + .describe('Present if the client supports elicitation from the server.') + ) + .optional(), + /** @description Present if the client supports task-augmented requests. */ + tasks: z + .looseObject({ + /** @description Whether this client supports tasks/list. */ + list: AssertObjectSchema.optional().describe('Whether this client supports tasks/list.'), + /** @description Whether this client supports tasks/cancel. */ + cancel: AssertObjectSchema.optional().describe('Whether this client supports tasks/cancel.'), + /** @description Specifies which request types can be augmented with tasks. */ + requests: z + .looseObject({ + /** @description Task support for sampling-related requests. */ + sampling: z + .looseObject({ + /** @description Whether the client supports task-augmented sampling/createMessage requests. */ + createMessage: AssertObjectSchema.optional().describe( + 'Whether the client supports task-augmented sampling/createMessage requests.' + ) + }) + .optional() + .describe('Task support for sampling-related requests.'), + /** @description Task support for elicitation-related requests. */ + elicitation: z + .looseObject({ + /** @description Whether the client supports task-augmented elicitation/create requests. */ + create: AssertObjectSchema.optional().describe( + 'Whether the client supports task-augmented elicitation/create requests.' + ) + }) + .optional() + .describe('Task support for elicitation-related requests.') + }) + .optional() + .describe('Specifies which request types can be augmented with tasks.') + }) + .optional() + .describe('Present if the client supports task-augmented requests.') + }) + .describe( + 'Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.' + ); + +/** + * @description Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. + * @category `initialize` + */ +export const ServerCapabilitiesSchema = z + .looseObject({ + /** @description Experimental, non-standard capabilities that the server supports. */ + experimental: z + .record(z.string(), AssertObjectSchema) + .optional() + .describe('Experimental, non-standard capabilities that the server supports.'), + /** @description Present if the server supports sending log messages to the client. */ + logging: AssertObjectSchema.optional().describe('Present if the server supports sending log messages to the client.'), + /** @description Present if the server supports argument autocompletion suggestions. */ + completions: AssertObjectSchema.optional().describe('Present if the server supports argument autocompletion suggestions.'), + /** @description Present if the server offers any prompt templates. */ + prompts: z + .looseObject({ + /** @description Whether this server supports notifications for changes to the prompt list. */ + listChanged: z.boolean().optional().describe('Whether this server supports notifications for changes to the prompt list.') + }) + .optional() + .describe('Present if the server offers any prompt templates.'), + /** @description Present if the server offers any resources to read. */ + resources: z + .looseObject({ + /** @description Whether this server supports subscribing to resource updates. */ + subscribe: z.boolean().optional().describe('Whether this server supports subscribing to resource updates.'), + /** @description Whether this server supports notifications for changes to the resource list. */ + listChanged: z.boolean().optional().describe('Whether this server supports notifications for changes to the resource list.') + }) + .optional() + .describe('Present if the server offers any resources to read.'), + /** @description Present if the server offers any tools to call. */ + tools: z + .looseObject({ + /** @description Whether this server supports notifications for changes to the tool list. */ + listChanged: z.boolean().optional().describe('Whether this server supports notifications for changes to the tool list.') + }) + .optional() + .describe('Present if the server offers any tools to call.'), + /** @description Present if the server supports task-augmented requests. */ + tasks: z + .looseObject({ + /** @description Whether this server supports tasks/list. */ + list: AssertObjectSchema.optional().describe('Whether this server supports tasks/list.'), + /** @description Whether this server supports tasks/cancel. */ + cancel: AssertObjectSchema.optional().describe('Whether this server supports tasks/cancel.'), + /** @description Specifies which request types can be augmented with tasks. */ + requests: z + .looseObject({ + /** @description Task support for tool-related requests. */ + tools: z + .looseObject({ + /** @description Whether the server supports task-augmented tools/call requests. */ + call: AssertObjectSchema.optional().describe( + 'Whether the server supports task-augmented tools/call requests.' + ) + }) + .optional() + .describe('Task support for tool-related requests.') + }) + .optional() + .describe('Specifies which request types can be augmented with tasks.') + }) + .optional() + .describe('Present if the server supports task-augmented requests.') + }) + .describe( + 'Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.' + ); + +/** + * @description This notification is sent from the client to the server after initialization has finished. + * @category `notifications/initialized` + */ +export const InitializedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/initialized'), + params: NotificationParamsSchema.optional() +}).describe('This notification is sent from the client to the server after initialization has finished.'); + +/** + * @description An optionally-sized icon that can be displayed in a user interface. + * @category Common Types + */ +export const IconSchema = z + .object({ + /** + * @description A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD takes steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + * @format uri + */ + src: z + .string() + .describe( + 'A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a\n`data:` URI with Base64-encoded image data.\n\nConsumers SHOULD takes steps to ensure URLs serving icons are from the\nsame domain as the client/server or a trusted domain.\n\nConsumers SHOULD take appropriate precautions when consuming SVGs as they can contain\nexecutable JavaScript.' + ), + /** @description Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. */ + mimeType: z + .string() + .optional() + .describe( + 'Optional MIME type override if the source MIME type is missing or generic.\nFor example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`.' + ), + /** @description Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. */ + sizes: z + .array(z.string()) + .optional() + .describe( + 'Optional array of strings that specify sizes at which the icon can be used.\nEach string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.' + ), + /** @description Optional specifier for the theme this icon is designed for. `light` indicates + the icon is designed to be used with a light background, and `dark` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. */ + theme: z + .enum(['light', 'dark']) + .optional() + .describe( + 'Optional specifier for the theme this icon is designed for. `light` indicates\nthe icon is designed to be used with a light background, and `dark` indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.' + ) + }) + .describe('An optionally-sized icon that can be displayed in a user interface.'); + +/** + * @description Base interface to add `icons` property. + * @internal + */ +export const IconsSchema = z + .object({ + /** @description Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) */ + icons: z + .array(IconSchema) + .optional() + .describe( + 'Optional set of sized icons that the client can display in a user interface.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)' + ) + }) + .describe('Base interface to add `icons` property.'); + +/** + * @description Base interface for metadata with name (identifier) and title (display name) properties. + * @internal + */ +export const BaseMetadataSchema = z + .object({ + /** @description Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). */ + name: z + .string() + .describe( + "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present)." + ), + /** @description Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). */ + title: z + .string() + .optional() + .describe( + 'Intended for UI and end-user contexts \u2014 optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).' + ) + }) + .describe('Base interface for metadata with name (identifier) and title (display name) properties.'); + +/** + * @description Describes the MCP implementation. + * @category `initialize` + */ +export const ImplementationSchema = BaseMetadataSchema.extend(IconsSchema.shape) + .extend({ + version: z.string(), + /** @description An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. */ + description: z + .string() + .optional() + .describe( + 'An optional human-readable description of what this implementation does.\n\nThis can be used by clients or servers to provide context about their purpose\nand capabilities. For example, a server might describe the types of resources\nor tools it provides, while a client might describe its intended use case.' + ), + /** + * @description An optional URL of the website for this implementation. + * @format uri + */ + websiteUrl: z.string().optional().describe('An optional URL of the website for this implementation.') + }) + .describe('Describes the MCP implementation.'); + +/* Ping */ +/** + * @description A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. + * @category `ping` + */ +export const PingRequestSchema = RequestSchema.extend({ + method: z.literal('ping'), + params: RequestParamsSchema.optional() +}).describe( + 'A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.' +); + +/* Progress notifications */ +/** + * @description Parameters for a `notifications/progress` notification. + * @category `notifications/progress` + */ +export const ProgressNotificationParamsSchema = NotificationParamsSchema.extend({ + /** @description The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. */ + progressToken: ProgressTokenSchema.describe( + 'The progress token which was given in the initial request, used to associate this notification with the request that is proceeding.' + ), + /** + * @description The progress thus far. This should increase every time progress is made, even if the total is unknown. + * @TJS-type number + */ + progress: z.number().describe('The progress thus far. This should increase every time progress is made, even if the total is unknown.'), + /** + * @description Total number of items to process (or total progress required), if known. + * @TJS-type number + */ + total: z.number().optional().describe('Total number of items to process (or total progress required), if known.'), + /** @description An optional message describing the current progress. */ + message: z.string().optional().describe('An optional message describing the current progress.') +}).describe('Parameters for a `notifications/progress` notification.'); + +/** + * @description An out-of-band notification used to inform the receiver of a progress update for a long-running request. + * @category `notifications/progress` + */ +export const ProgressNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/progress'), + params: ProgressNotificationParamsSchema +}).describe('An out-of-band notification used to inform the receiver of a progress update for a long-running request.'); + +/* Pagination */ +/** + * @description Common parameters for paginated requests. + * @internal + */ +export const PaginatedRequestParamsSchema = RequestParamsSchema.extend({ + /** @description An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. */ + cursor: CursorSchema.optional().describe( + 'An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.' + ) +}).describe('Common parameters for paginated requests.'); + +/** @internal */ +export const PaginatedRequestSchema = RequestSchema.extend({ + params: PaginatedRequestParamsSchema.optional() +}); + +/** @internal */ +export const PaginatedResultSchema = ResultSchema.extend({ + /** @description An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. */ + nextCursor: CursorSchema.optional().describe( + 'An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.' + ) +}); + +/* Resources */ +/** + * @description Sent from the client to request a list of resources the server has. + * @category `resources/list` + */ +export const ListResourcesRequestSchema = PaginatedRequestSchema.extend({ + method: z.literal('resources/list') +}).describe('Sent from the client to request a list of resources the server has.'); + +/** + * @description Sent from the client to request a list of resource templates the server has. + * @category `resources/templates/list` + */ +export const ListResourceTemplatesRequestSchema = PaginatedRequestSchema.extend({ + method: z.literal('resources/templates/list') +}).describe('Sent from the client to request a list of resource templates the server has.'); + +/** + * @description Common parameters when working with resources. + * @internal + */ +export const ResourceRequestParamsSchema = RequestParamsSchema.extend({ + /** + * @description The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + * @format uri + */ + uri: z.string().describe('The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.') +}).describe('Common parameters when working with resources.'); + +/** + * @description Parameters for a `resources/read` request. + * @category `resources/read` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export const ReadResourceRequestParamsSchema = ResourceRequestParamsSchema.describe('Parameters for a `resources/read` request.'); + +/** + * @description Sent from the client to the server, to read a specific resource URI. + * @category `resources/read` + */ +export const ReadResourceRequestSchema = RequestSchema.extend({ + method: z.literal('resources/read'), + params: ReadResourceRequestParamsSchema +}).describe('Sent from the client to the server, to read a specific resource URI.'); + +/** + * @description An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/resources/list_changed` + */ +export const ResourceListChangedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/resources/list_changed'), + params: NotificationParamsSchema.optional() +}).describe( + 'An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.' +); + +/** + * @description Parameters for a `resources/subscribe` request. + * @category `resources/subscribe` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export const SubscribeRequestParamsSchema = ResourceRequestParamsSchema.describe('Parameters for a `resources/subscribe` request.'); + +/** + * @description Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. + * @category `resources/subscribe` + */ +export const SubscribeRequestSchema = RequestSchema.extend({ + method: z.literal('resources/subscribe'), + params: SubscribeRequestParamsSchema +}).describe('Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.'); + +/** + * @description Parameters for a `resources/unsubscribe` request. + * @category `resources/unsubscribe` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export const UnsubscribeRequestParamsSchema = ResourceRequestParamsSchema.describe('Parameters for a `resources/unsubscribe` request.'); + +/** + * @description Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. + * @category `resources/unsubscribe` + */ +export const UnsubscribeRequestSchema = RequestSchema.extend({ + method: z.literal('resources/unsubscribe'), + params: UnsubscribeRequestParamsSchema +}).describe( + 'Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.' +); + +/** + * @description Parameters for a `notifications/resources/updated` notification. + * @category `notifications/resources/updated` + */ +export const ResourceUpdatedNotificationParamsSchema = NotificationParamsSchema.extend({ + /** + * @description The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + * @format uri + */ + uri: z + .string() + .describe( + 'The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.' + ) +}).describe('Parameters for a `notifications/resources/updated` notification.'); + +/** + * @description A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. + * @category `notifications/resources/updated` + */ +export const ResourceUpdatedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/resources/updated'), + params: ResourceUpdatedNotificationParamsSchema +}).describe( + 'A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.' +); + +/** + * @description The contents of a specific resource or sub-resource. + * @internal + */ +export const ResourceContentsSchema = z + .object({ + /** + * @description The URI of this resource. + * @format uri + */ + uri: z.string().describe('The URI of this resource.'), + /** @description The MIME type of this resource, if known. */ + mimeType: z.string().optional().describe('The MIME type of this resource, if known.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('The contents of a specific resource or sub-resource.'); + +/** + * @category Content + */ +export const TextResourceContentsSchema = ResourceContentsSchema.extend({ + /** @description The text of the item. This must only be set if the item can actually be represented as text (not binary data). */ + text: z + .string() + .describe('The text of the item. This must only be set if the item can actually be represented as text (not binary data).') +}); + +/** + * @category Content + */ +export const BlobResourceContentsSchema = ResourceContentsSchema.extend({ + /** + * @description A base64-encoded string representing the binary data of the item. + * @format byte + */ + blob: z.string().refine( + val => { + try { + atob(val); + return true; + } catch { + return false; + } + }, + { message: 'Invalid base64 string' } + ) +}); + +/* Prompts */ +/** + * @description Sent from the client to request a list of prompts and prompt templates the server has. + * @category `prompts/list` + */ +export const ListPromptsRequestSchema = PaginatedRequestSchema.extend({ + method: z.literal('prompts/list') +}).describe('Sent from the client to request a list of prompts and prompt templates the server has.'); + +/** + * @description Parameters for a `prompts/get` request. + * @category `prompts/get` + */ +export const GetPromptRequestParamsSchema = RequestParamsSchema.extend({ + /** @description The name of the prompt or prompt template. */ + name: z.string().describe('The name of the prompt or prompt template.'), + /** @description Arguments to use for templating the prompt. */ + arguments: z.record(z.string(), z.string()).optional().describe('Arguments to use for templating the prompt.') +}).describe('Parameters for a `prompts/get` request.'); + +/** + * @description Used by the client to get a prompt provided by the server. + * @category `prompts/get` + */ +export const GetPromptRequestSchema = RequestSchema.extend({ + method: z.literal('prompts/get'), + params: GetPromptRequestParamsSchema +}).describe('Used by the client to get a prompt provided by the server.'); + +/** + * @description Describes an argument that a prompt can accept. + * @category `prompts/list` + */ +export const PromptArgumentSchema = BaseMetadataSchema.extend({ + /** @description A human-readable description of the argument. */ + description: z.string().optional().describe('A human-readable description of the argument.'), + /** @description Whether this argument must be provided. */ + required: z.boolean().optional().describe('Whether this argument must be provided.') +}).describe('Describes an argument that a prompt can accept.'); + +/** + * @description The sender or recipient of messages and data in a conversation. + * @category Common Types + */ +export const RoleSchema = z.enum(['user', 'assistant']).describe('The sender or recipient of messages and data in a conversation.'); + +/** + * @description Optional annotations for the client. The client can use annotations to inform how objects are used or displayed + * @category Common Types + */ +export const AnnotationsSchema = z + .object({ + /** @description Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). */ + audience: z + .array(RoleSchema) + .optional() + .describe( + 'Describes who the intended audience of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).' + ), + /** + * @description Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + priority: z + .number() + .min(0) + .max(1) + .optional() + .describe( + 'Describes how important this data is for operating the server.\n\nA value of 1 means "most important," and indicates that the data is\neffectively required, while 0 means "least important," and indicates that\nthe data is entirely optional.' + ), + /** @description The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. */ + lastModified: z.iso.datetime({ offset: true }).optional() + }) + .describe('Optional annotations for the client. The client can use annotations to inform how objects are used or displayed'); + +/** + * @description An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/prompts/list_changed` + */ +export const PromptListChangedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/prompts/list_changed'), + params: NotificationParamsSchema.optional() +}).describe( + 'An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.' +); + +/* Tools */ +/** + * @description Sent from the client to request a list of tools the server has. + * @category `tools/list` + */ +export const ListToolsRequestSchema = PaginatedRequestSchema.extend({ + method: z.literal('tools/list') +}).describe('Sent from the client to request a list of tools the server has.'); + +/** + * @description Common params for any task-augmented request. + * @internal + */ +export const TaskAugmentedRequestParamsSchema = RequestParamsSchema.extend({ + /** @description If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. */ + task: TaskMetadataSchema.optional().describe( + 'If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities.' + ) +}).describe('Common params for any task-augmented request.'); + +/** + * @description Parameters for a `tools/call` request. + * @category `tools/call` + */ +export const CallToolRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({ + /** @description The name of the tool. */ + name: z.string().describe('The name of the tool.'), + /** @description Arguments to use for the tool call. */ + arguments: z.record(z.string(), z.unknown()).optional().describe('Arguments to use for the tool call.') +}).describe('Parameters for a `tools/call` request.'); + +/** + * @description An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/tools/list_changed` + */ +export const ToolListChangedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/tools/list_changed'), + params: NotificationParamsSchema.optional() +}).describe( + 'An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.' +); + +/** + * @description Additional properties describing a Tool to clients. + +NOTE: all properties in ToolAnnotations are **hints**. +They are not guaranteed to provide a faithful description of +tool behavior (including descriptive properties like `title`). + +Clients should never make tool use decisions based on ToolAnnotations +received from untrusted servers. + * @category `tools/list` + */ +export const ToolAnnotationsSchema = z + .object({ + /** @description A human-readable title for the tool. */ + title: z.string().optional().describe('A human-readable title for the tool.'), + /** @description If true, the tool does not modify its environment. + + Default: false */ + readOnlyHint: z.boolean().optional().describe('If true, the tool does not modify its environment.\n\nDefault: false'), + /** @description If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true */ + destructiveHint: z + .boolean() + .optional() + .describe( + 'If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true' + ), + /** @description If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false */ + idempotentHint: z + .boolean() + .optional() + .describe( + 'If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false' + ), + /** @description If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true */ + openWorldHint: z + .boolean() + .optional() + .describe( + 'If true, this tool may interact with an "open world" of external\nentities. If false, the tool\'s domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true' + ) + }) + .describe( + 'Additional properties describing a Tool to clients. NOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like `title`). Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers.' + ); + +/** + * @description Execution-related properties for a tool. + * @category `tools/list` + */ +export const ToolExecutionSchema = z + .object({ + /** @description Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" */ + taskSupport: z + .enum(['forbidden', 'optional', 'required']) + .optional() + .describe( + 'Indicates whether this tool supports task-augmented execution.\nThis allows clients to handle long-running operations through polling\nthe task system.\n\n- "forbidden": Tool does not support task-augmented execution (default when absent)\n- "optional": Tool may support task-augmented execution\n- "required": Tool requires task-augmented execution\n\nDefault: "forbidden"' + ) + }) + .describe('Execution-related properties for a tool.'); + +/** + * @description Definition for a tool the client can call. + * @category `tools/list` + */ +export const ToolSchema = BaseMetadataSchema.extend(IconsSchema.shape) + .extend({ + /** @description A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. */ + description: z + .string() + .optional() + .describe( + 'A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools. It can be thought of like a "hint" to the model.' + ), + /** @description A JSON Schema object defining the expected parameters for the tool. */ + inputSchema: z + .object({ + $schema: z.string().optional(), + type: z.literal('object'), + properties: z.record(z.string(), z.record(z.string(), z.any())).optional(), + required: z.array(z.string()).optional() + }) + .describe('A JSON Schema object defining the expected parameters for the tool.'), + /** @description Execution-related properties for this tool. */ + execution: ToolExecutionSchema.optional().describe('Execution-related properties for this tool.'), + /** @description An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. */ + outputSchema: z + .object({ + $schema: z.string().optional(), + type: z.literal('object'), + properties: z.record(z.string(), z.record(z.string(), z.any())).optional(), + required: z.array(z.string()).optional() + }) + .passthrough() + .optional() + .describe( + 'An optional JSON Schema object defining the structure of the tool\'s output returned in\nthe structuredContent field of a CallToolResult.\n\nDefaults to JSON Schema 2020-12 when no explicit $schema is provided.\nCurrently restricted to type: "object" at the root level.' + ), + /** @description Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. */ + annotations: ToolAnnotationsSchema.optional().describe( + 'Optional additional tool information.\n\nDisplay name precedence order is: title, annotations.title, then name.' + ), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Definition for a tool the client can call.'); + +/* Tasks */ +/** + * @description The status of a task. + * @category `tasks` + */ +export const TaskStatusSchema = z.enum(['working', 'input_required', 'completed', 'failed', 'cancelled']).describe('The status of a task.'); + +/** + * @description Data associated with a task. + * @category `tasks` + */ +export const TaskSchema = z + .object({ + /** @description The task identifier. */ + taskId: z.string().describe('The task identifier.'), + /** @description Current task state. */ + status: TaskStatusSchema.describe('Current task state.'), + /** @description Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) */ + statusMessage: z + .string() + .optional() + .describe( + 'Optional human-readable message describing the current task state.\nThis can provide context for any status, including:\n- Reasons for "cancelled" status\n- Summaries for "completed" status\n- Diagnostic information for "failed" status (e.g., error details, what went wrong)' + ), + /** @description ISO 8601 timestamp when the task was created. */ + createdAt: z.string().describe('ISO 8601 timestamp when the task was created.'), + /** @description ISO 8601 timestamp when the task was last updated. */ + lastUpdatedAt: z.string().describe('ISO 8601 timestamp when the task was last updated.'), + /** @description Actual retention duration from creation in milliseconds, null for unlimited. */ + ttl: z.number().nullable().describe('Actual retention duration from creation in milliseconds, null for unlimited.'), + /** @description Suggested polling interval in milliseconds. */ + pollInterval: z.number().optional().describe('Suggested polling interval in milliseconds.') + }) + .describe('Data associated with a task.'); + +/** + * @description A response to a task-augmented request. + * @category `tasks` + */ +export const CreateTaskResultSchema = ResultSchema.extend({ + task: TaskSchema +}).describe('A response to a task-augmented request.'); + +/** + * @description A request to retrieve the state of a task. + * @category `tasks/get` + */ +export const GetTaskRequestSchema = RequestSchema.extend({ + method: z.literal('tasks/get'), + params: z.object({ + /** @description The task identifier to query. */ + taskId: z.string().describe('The task identifier to query.') + }) +}).describe('A request to retrieve the state of a task.'); + +/** + * @description The response to a tasks/get request. + * @category `tasks/get` + */ +export const GetTaskResultSchema = ResultSchema.and(TaskSchema).describe('The response to a tasks/get request.'); + +/** + * @description A request to retrieve the result of a completed task. + * @category `tasks/result` + */ +export const GetTaskPayloadRequestSchema = RequestSchema.extend({ + method: z.literal('tasks/result'), + params: z.object({ + /** @description The task identifier to retrieve results for. */ + taskId: z.string().describe('The task identifier to retrieve results for.') + }) +}).describe('A request to retrieve the result of a completed task.'); + +/** + * @description The response to a tasks/result request. +The structure matches the result type of the original request. +For example, a tools/call task would return the CallToolResult structure. + * @category `tasks/result` + */ +export const GetTaskPayloadResultSchema = ResultSchema.and(z.record(z.string(), z.unknown())).describe( + 'The response to a tasks/result request. The structure matches the result type of the original request. For example, a tools/call task would return the CallToolResult structure.' +); + +/** + * @description A request to cancel a task. + * @category `tasks/cancel` + */ +export const CancelTaskRequestSchema = RequestSchema.extend({ + method: z.literal('tasks/cancel'), + params: z.object({ + /** @description The task identifier to cancel. */ + taskId: z.string().describe('The task identifier to cancel.') + }) +}).describe('A request to cancel a task.'); + +/** + * @description The response to a tasks/cancel request. + * @category `tasks/cancel` + */ +export const CancelTaskResultSchema = ResultSchema.and(TaskSchema).describe('The response to a tasks/cancel request.'); + +/** + * @description A request to retrieve a list of tasks. + * @category `tasks/list` + */ +export const ListTasksRequestSchema = PaginatedRequestSchema.extend({ + method: z.literal('tasks/list') +}).describe('A request to retrieve a list of tasks.'); + +/** + * @description The response to a tasks/list request. + * @category `tasks/list` + */ +export const ListTasksResultSchema = PaginatedResultSchema.extend({ + tasks: z.array(TaskSchema) +}).describe('The response to a tasks/list request.'); + +/** + * @description Parameters for a `notifications/tasks/status` notification. + * @category `notifications/tasks/status` + */ +export const TaskStatusNotificationParamsSchema = NotificationParamsSchema.and(TaskSchema).describe( + 'Parameters for a `notifications/tasks/status` notification.' +); + +/** + * @description An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications. + * @category `notifications/tasks/status` + */ +export const TaskStatusNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/tasks/status'), + params: TaskStatusNotificationParamsSchema +}).describe( + "An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications." +); + +/** + * @description The severity of a log message. + +These map to syslog message severities, as specified in RFC-5424: +https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + * @category Common Types + */ +export const LoggingLevelSchema = z + .enum(['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']) + .describe( + 'The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1' + ); + +/* Logging */ +/** + * @description Parameters for a `logging/setLevel` request. + * @category `logging/setLevel` + */ +export const SetLevelRequestParamsSchema = RequestParamsSchema.extend({ + /** @description The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. */ + level: LoggingLevelSchema.describe( + 'The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message.' + ) +}).describe('Parameters for a `logging/setLevel` request.'); + +/** + * @description Parameters for a `notifications/message` notification. + * @category `notifications/message` + */ +export const LoggingMessageNotificationParamsSchema = NotificationParamsSchema.extend({ + /** @description The severity of this log message. */ + level: LoggingLevelSchema.describe('The severity of this log message.'), + /** @description An optional name of the logger issuing this message. */ + logger: z.string().optional().describe('An optional name of the logger issuing this message.'), + /** @description The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. */ + data: z.unknown().describe('The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.') +}).describe('Parameters for a `notifications/message` notification.'); + +/** + * @description JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically. + * @category `notifications/message` + */ +export const LoggingMessageNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/message'), + params: LoggingMessageNotificationParamsSchema +}).describe( + 'JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.' +); + +/** + * @description Controls tool selection behavior for sampling requests. + * @category `sampling/createMessage` + */ +export const ToolChoiceSchema = z + .object({ + /** @description Controls the tool use ability of the model: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model MUST NOT use any tools */ + mode: z + .enum(['auto', 'required', 'none']) + .optional() + .describe( + 'Controls the tool use ability of the model:\n- "auto": Model decides whether to use tools (default)\n- "required": Model MUST use at least one tool before completing\n- "none": Model MUST NOT use any tools' + ) + }) + .describe('Controls tool selection behavior for sampling requests.'); + +/** + * @description Text provided to or from an LLM. + * @category Content + */ +export const TextContentSchema = z + .object({ + type: z.literal('text'), + /** @description The text content of the message. */ + text: z.string().describe('The text content of the message.'), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Text provided to or from an LLM.'); + +/** + * @description An image provided to or from an LLM. + * @category Content + */ +export const ImageContentSchema = z + .object({ + type: z.literal('image'), + /** + * @description The base64-encoded image data. + * @format byte + */ + data: z.string().refine( + val => { + try { + atob(val); + return true; + } catch { + return false; + } + }, + { message: 'Invalid base64 string' } + ), + /** @description The MIME type of the image. Different providers may support different image types. */ + mimeType: z.string().describe('The MIME type of the image. Different providers may support different image types.'), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('An image provided to or from an LLM.'); + +/** + * @description Audio provided to or from an LLM. + * @category Content + */ +export const AudioContentSchema = z + .object({ + type: z.literal('audio'), + /** + * @description The base64-encoded audio data. + * @format byte + */ + data: z.string().refine( + val => { + try { + atob(val); + return true; + } catch { + return false; + } + }, + { message: 'Invalid base64 string' } + ), + /** @description The MIME type of the audio. Different providers may support different audio types. */ + mimeType: z.string().describe('The MIME type of the audio. Different providers may support different audio types.'), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Audio provided to or from an LLM.'); + +/** + * @description A request from the assistant to call a tool. + * @category `sampling/createMessage` + */ +export const ToolUseContentSchema = z + .object({ + type: z.literal('tool_use'), + /** @description A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. */ + id: z + .string() + .describe('A unique identifier for this tool use.\n\nThis ID is used to match tool results to their corresponding tool uses.'), + /** @description The name of the tool to call. */ + name: z.string().describe('The name of the tool to call.'), + /** @description The arguments to pass to the tool, conforming to the tool's input schema. */ + input: z.record(z.string(), z.unknown()).describe("The arguments to pass to the tool, conforming to the tool's input schema."), + /** @description Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe( + 'Optional metadata about the tool use. Clients SHOULD preserve this field when\nincluding tool uses in subsequent sampling requests to enable caching optimizations.\n\nSee [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.' + ) + }) + .describe('A request from the assistant to call a tool.'); + +/** + * @description The contents of a resource, embedded into a prompt or tool call result. + +It is up to the client how best to render embedded resources for the benefit +of the LLM and/or the user. + * @category Content + */ +export const EmbeddedResourceSchema = z + .object({ + type: z.literal('resource'), + resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe( + 'The contents of a resource, embedded into a prompt or tool call result. It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user.' + ); + +/** + * @description Hints to use for model selection. + +Keys not declared here are currently left unspecified by the spec and are up +to the client to interpret. + * @category `sampling/createMessage` + */ +export const ModelHintSchema = z + .object({ + /** @description A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` */ + name: z + .string() + .optional() + .describe( + "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n- `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n- `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n- `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:\n- `gemini-1.5-flash` could match `claude-3-haiku-20240307`" + ) + }) + .describe( + 'Hints to use for model selection. Keys not declared here are currently left unspecified by the spec and are up to the client to interpret.' + ); + +/** + * @description Identifies a prompt. + * @category `completion/complete` + */ +export const PromptReferenceSchema = BaseMetadataSchema.extend({ + type: z.literal('ref/prompt') +}).describe('Identifies a prompt.'); + +/** + * @description A reference to a resource or resource template definition. + * @category `completion/complete` + */ +export const ResourceTemplateReferenceSchema = z + .object({ + type: z.literal('ref/resource'), + /** + * @description The URI or URI template of the resource. + * @format uri-template + */ + uri: z.string().describe('The URI or URI template of the resource.') + }) + .describe('A reference to a resource or resource template definition.'); + +/* Autocomplete */ +/** + * @description Parameters for a `completion/complete` request. + * @category `completion/complete` + */ +export const CompleteRequestParamsSchema = RequestParamsSchema.extend({ + ref: z.union([PromptReferenceSchema, ResourceTemplateReferenceSchema]), + /** @description The argument's information */ + argument: z + .object({ + /** @description The name of the argument */ + name: z.string().describe('The name of the argument'), + /** @description The value of the argument to use for completion matching. */ + value: z.string().describe('The value of the argument to use for completion matching.') + }) + .describe("The argument's information"), + /** @description Additional, optional context for completions */ + context: z + .object({ + /** @description Previously-resolved variables in a URI template or prompt. */ + arguments: z.record(z.string(), z.string()).optional().describe('Previously-resolved variables in a URI template or prompt.') + }) + .optional() + .describe('Additional, optional context for completions') +}).describe('Parameters for a `completion/complete` request.'); + +/** + * @description The server's response to a completion/complete request + * @category `completion/complete` + */ +export const CompleteResultSchema = ResultSchema.extend({ + completion: z.object({ + /** @description An array of completion values. Must not exceed 100 items. */ + values: z.array(z.string()).describe('An array of completion values. Must not exceed 100 items.'), + /** @description The total number of completion options available. This can exceed the number of values actually sent in the response. */ + total: z + .number() + .optional() + .describe( + 'The total number of completion options available. This can exceed the number of values actually sent in the response.' + ), + /** @description Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. */ + hasMore: z + .boolean() + .optional() + .describe( + 'Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.' + ) + }) +}).describe("The server's response to a completion/complete request"); + +/* Roots */ +/** + * @description Sent from the server to request a list of root URIs from the client. Roots allow +servers to ask for specific directories or files to operate on. A common example +for roots is providing a set of repositories or directories a server should operate +on. + +This request is typically used when the server needs to understand the file system +structure or access specific locations that the client has permission to read from. + * @category `roots/list` + */ +export const ListRootsRequestSchema = RequestSchema.extend({ + method: z.literal('roots/list'), + params: RequestParamsSchema.optional() +}).describe( + 'Sent from the server to request a list of root URIs from the client. Roots allow servers to ask for specific directories or files to operate on. A common example for roots is providing a set of repositories or directories a server should operate on. This request is typically used when the server needs to understand the file system structure or access specific locations that the client has permission to read from.' +); + +/** + * @description Represents a root directory or file that the server can operate on. + * @category `roots/list` + */ +export const RootSchema = z + .object({ + /** + * @description The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + * @format uri + */ + uri: z.string().startsWith('file://'), + /** @description An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. */ + name: z + .string() + .optional() + .describe( + 'An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.' + ), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Represents a root directory or file that the server can operate on.'); + +/** + * @description A notification from the client to the server, informing it that the list of roots has changed. +This notification should be sent whenever the client adds, removes, or modifies any root. +The server should then request an updated list of roots using the ListRootsRequest. + * @category `notifications/roots/list_changed` + */ +export const RootsListChangedNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/roots/list_changed'), + params: NotificationParamsSchema.optional() +}).describe( + 'A notification from the client to the server, informing it that the list of roots has changed. This notification should be sent whenever the client adds, removes, or modifies any root. The server should then request an updated list of roots using the ListRootsRequest.' +); + +/** + * @description The parameters for a request to elicit information from the user via a URL in the client. + * @category `elicitation/create` + */ +export const ElicitRequestURLParamsSchema = TaskAugmentedRequestParamsSchema.extend({ + /** @description The elicitation mode. */ + mode: z.literal('url').describe('The elicitation mode.'), + /** @description The message to present to the user explaining why the interaction is needed. */ + message: z.string().describe('The message to present to the user explaining why the interaction is needed.'), + /** @description The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. */ + elicitationId: z + .string() + .describe( + 'The ID of the elicitation, which must be unique within the context of the server.\nThe client MUST treat this ID as an opaque value.' + ), + /** + * @description The URL that the user should navigate to. + * @format uri + */ + url: z.string().describe('The URL that the user should navigate to.') +}).describe('The parameters for a request to elicit information from the user via a URL in the client.'); + +/** + * @category `elicitation/create` + */ +export const StringSchemaSchema = z.object({ + type: z.literal('string'), + title: z.string().optional(), + description: z.string().optional(), + minLength: z.number().optional(), + maxLength: z.number().optional(), + format: z.enum(['email', 'uri', 'date', 'date-time']).optional(), + default: z.string().optional() +}); + +/** + * @category `elicitation/create` + */ +export const NumberSchemaSchema = z.object({ + type: z.enum(['number', 'integer']), + title: z.string().optional(), + description: z.string().optional(), + minimum: z.number().optional(), + maximum: z.number().optional(), + default: z.number().optional() +}); + +/** + * @category `elicitation/create` + */ +export const BooleanSchemaSchema = z.object({ + type: z.literal('boolean'), + title: z.string().optional(), + description: z.string().optional(), + default: z.boolean().optional() +}); + +/** + * @description Schema for single-selection enumeration without display titles for options. + * @category `elicitation/create` + */ +export const UntitledSingleSelectEnumSchemaSchema = z + .object({ + type: z.literal('string'), + /** @description Optional title for the enum field. */ + title: z.string().optional().describe('Optional title for the enum field.'), + /** @description Optional description for the enum field. */ + description: z.string().optional().describe('Optional description for the enum field.'), + /** @description Array of enum values to choose from. */ + enum: z.array(z.string()).describe('Array of enum values to choose from.'), + /** @description Optional default value. */ + default: z.string().optional().describe('Optional default value.') + }) + .describe('Schema for single-selection enumeration without display titles for options.'); + +/** + * @description Schema for single-selection enumeration with display titles for each option. + * @category `elicitation/create` + */ +export const TitledSingleSelectEnumSchemaSchema = z + .object({ + type: z.literal('string'), + /** @description Optional title for the enum field. */ + title: z.string().optional().describe('Optional title for the enum field.'), + /** @description Optional description for the enum field. */ + description: z.string().optional().describe('Optional description for the enum field.'), + /** @description Array of enum options with values and display labels. */ + oneOf: z + .array( + z.object({ + /** + * The enum value. + */ + const: z.string(), + /** + * Display label for this option. + */ + title: z.string() + }) + ) + .describe('Array of enum options with values and display labels.'), + /** @description Optional default value. */ + default: z.string().optional().describe('Optional default value.') + }) + .describe('Schema for single-selection enumeration with display titles for each option.'); + +/** + * @category `elicitation/create` + */ +// Combined single selection enumeration +export const SingleSelectEnumSchemaSchema = z.union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]); + +/** + * @description Schema for multiple-selection enumeration without display titles for options. + * @category `elicitation/create` + */ +export const UntitledMultiSelectEnumSchemaSchema = z + .object({ + type: z.literal('array'), + /** @description Optional title for the enum field. */ + title: z.string().optional().describe('Optional title for the enum field.'), + /** @description Optional description for the enum field. */ + description: z.string().optional().describe('Optional description for the enum field.'), + /** @description Minimum number of items to select. */ + minItems: z.number().optional().describe('Minimum number of items to select.'), + /** @description Maximum number of items to select. */ + maxItems: z.number().optional().describe('Maximum number of items to select.'), + /** @description Schema for the array items. */ + items: z + .object({ + type: z.literal('string'), + /** @description Array of enum values to choose from. */ + enum: z.array(z.string()).describe('Array of enum values to choose from.') + }) + .describe('Schema for the array items.'), + /** @description Optional default value. */ + default: z.array(z.string()).optional().describe('Optional default value.') + }) + .describe('Schema for multiple-selection enumeration without display titles for options.'); + +/** + * @description Schema for multiple-selection enumeration with display titles for each option. + * @category `elicitation/create` + */ +export const TitledMultiSelectEnumSchemaSchema = z + .object({ + type: z.literal('array'), + /** @description Optional title for the enum field. */ + title: z.string().optional().describe('Optional title for the enum field.'), + /** @description Optional description for the enum field. */ + description: z.string().optional().describe('Optional description for the enum field.'), + /** @description Minimum number of items to select. */ + minItems: z.number().optional().describe('Minimum number of items to select.'), + /** @description Maximum number of items to select. */ + maxItems: z.number().optional().describe('Maximum number of items to select.'), + /** @description Schema for array items with enum options and display labels. */ + items: z + .object({ + /** @description Array of enum options with values and display labels. */ + anyOf: z + .array( + z.object({ + /** + * The constant enum value. + */ + const: z.string(), + /** + * Display title for this option. + */ + title: z.string() + }) + ) + .describe('Array of enum options with values and display labels.') + }) + .describe('Schema for array items with enum options and display labels.'), + /** @description Optional default value. */ + default: z.array(z.string()).optional().describe('Optional default value.') + }) + .describe('Schema for multiple-selection enumeration with display titles for each option.'); + +/** + * @category `elicitation/create` + */ +// Combined multiple selection enumeration +export const MultiSelectEnumSchemaSchema = z.union([UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema]); + +/** + * @description Use TitledSingleSelectEnumSchema instead. +This interface will be removed in a future version. + * @category `elicitation/create` + */ +export const LegacyTitledEnumSchemaSchema = z + .object({ + type: z.literal('string'), + title: z.string().optional(), + description: z.string().optional(), + enum: z.array(z.string()), + /** @description (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. */ + enumNames: z + .array(z.string()) + .optional() + .describe('(Legacy) Display names for enum values.\nNon-standard according to JSON schema 2020-12.'), + default: z.string().optional() + }) + .describe('Use TitledSingleSelectEnumSchema instead. This interface will be removed in a future version.'); + +/** + * @category `elicitation/create` + */ +// Union type for all enum schemas +export const EnumSchemaSchema = z.union([LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema]); + +/** + * @description The client's response to an elicitation request. + * @category `elicitation/create` + */ +export const ElicitResultSchema = ResultSchema.extend({ + /** @description The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly decline the action + - "cancel": User dismissed without making an explicit choice */ + action: z + .enum(['accept', 'decline', 'cancel']) + .describe( + 'The user action in response to the elicitation.\n- "accept": User submitted the form/confirmed the action\n- "decline": User explicitly decline the action\n- "cancel": User dismissed without making an explicit choice' + ), + /** @description The submitted form data, only present when action is "accept" and mode was "form". + Contains values matching the requested schema. + Omitted for out-of-band mode responses. */ + content: z + .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())])) + .optional() + .describe( + 'The submitted form data, only present when action is "accept" and mode was "form".\nContains values matching the requested schema.\nOmitted for out-of-band mode responses.' + ) +}).describe("The client's response to an elicitation request."); + +/** + * @description An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. + * @category `notifications/elicitation/complete` + */ +export const ElicitationCompleteNotificationSchema = NotificationSchema.extend({ + method: z.literal('notifications/elicitation/complete'), + params: z.object({ + /** @description The ID of the elicitation that completed. */ + elicitationId: z.string().describe('The ID of the elicitation that completed.') + }) +}).describe('An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.'); + +/** + * @description A request from the client to the server, to ask for completion options. + * @category `completion/complete` + */ +export const CompleteRequestSchema = RequestSchema.extend({ + method: z.literal('completion/complete'), + params: CompleteRequestParamsSchema +}).describe('A request from the client to the server, to ask for completion options.'); + +/** + * @description A request from the client to the server, to enable or adjust logging. + * @category `logging/setLevel` + */ +export const SetLevelRequestSchema = RequestSchema.extend({ + method: z.literal('logging/setLevel'), + params: SetLevelRequestParamsSchema +}).describe('A request from the client to the server, to enable or adjust logging.'); + +/** + * @description Used by the client to invoke a tool provided by the server. + * @category `tools/call` + */ +export const CallToolRequestSchema = RequestSchema.extend({ + method: z.literal('tools/call'), + params: CallToolRequestParamsSchema +}).describe('Used by the client to invoke a tool provided by the server.'); + +/** @internal */ +export const ClientNotificationSchema = z.union([ + CancelledNotificationSchema, + ProgressNotificationSchema, + InitializedNotificationSchema, + RootsListChangedNotificationSchema, + TaskStatusNotificationSchema +]); + +/** + * @description The client's response to a roots/list request from the server. +This result contains an array of Root objects, each representing a root directory +or file that the server can operate on. + * @category `roots/list` + */ +export const ListRootsResultSchema = ResultSchema.extend({ + roots: z.array(RootSchema) +}).describe( + "The client's response to a roots/list request from the server. This result contains an array of Root objects, each representing a root directory or file that the server can operate on." +); + +/** @internal */ +export const ServerNotificationSchema = z.union([ + CancelledNotificationSchema, + ProgressNotificationSchema, + LoggingMessageNotificationSchema, + ResourceUpdatedNotificationSchema, + ResourceListChangedNotificationSchema, + ToolListChangedNotificationSchema, + PromptListChangedNotificationSchema, + ElicitationCompleteNotificationSchema, + TaskStatusNotificationSchema +]); + +/** + * @description After receiving an initialize request from the client, the server sends this response. + * @category `initialize` + */ +export const InitializeResultSchema = ResultSchema.extend({ + /** @description The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. */ + protocolVersion: z + .string() + .describe( + 'The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.' + ), + capabilities: ServerCapabilitiesSchema, + serverInfo: ImplementationSchema, + /** @description Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. */ + instructions: z + .string() + .optional() + .describe( + 'Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.' + ) +}).describe('After receiving an initialize request from the client, the server sends this response.'); + +/** + * @description The server's response to a resources/read request from the client. + * @category `resources/read` + */ +export const ReadResourceResultSchema = ResultSchema.extend({ + contents: z.array(z.union([TextResourceContentsSchema, BlobResourceContentsSchema])) +}).describe("The server's response to a resources/read request from the client."); + +/** + * @description The server's response to a tools/list request from the client. + * @category `tools/list` + */ +export const ListToolsResultSchema = PaginatedResultSchema.extend({ + tools: z.array(ToolSchema) +}).describe("The server's response to a tools/list request from the client."); + +/** Extracted from ClientCapabilities["tasks"]. */ +export const ClientTasksCapabilitySchema = z.looseObject({ + /** @description Whether this client supports tasks/list. */ + list: AssertObjectSchema.optional().describe('Whether this client supports tasks/list.'), + /** @description Whether this client supports tasks/cancel. */ + cancel: AssertObjectSchema.optional().describe('Whether this client supports tasks/cancel.'), + /** @description Specifies which request types can be augmented with tasks. */ + requests: z + .looseObject({ + /** @description Task support for sampling-related requests. */ + sampling: z + .looseObject({ + /** @description Whether the client supports task-augmented sampling/createMessage requests. */ + createMessage: AssertObjectSchema.optional().describe( + 'Whether the client supports task-augmented sampling/createMessage requests.' + ) + }) + .optional() + .describe('Task support for sampling-related requests.'), + /** @description Task support for elicitation-related requests. */ + elicitation: z + .looseObject({ + /** @description Whether the client supports task-augmented elicitation/create requests. */ + create: AssertObjectSchema.optional().describe( + 'Whether the client supports task-augmented elicitation/create requests.' + ) + }) + .optional() + .describe('Task support for elicitation-related requests.') + }) + .optional() + .describe('Specifies which request types can be augmented with tasks.') +}); + +/** Extracted from ServerCapabilities["tasks"]. */ +export const ServerTasksCapabilitySchema = z.looseObject({ + /** @description Whether this server supports tasks/list. */ + list: AssertObjectSchema.optional().describe('Whether this server supports tasks/list.'), + /** @description Whether this server supports tasks/cancel. */ + cancel: AssertObjectSchema.optional().describe('Whether this server supports tasks/cancel.'), + /** @description Specifies which request types can be augmented with tasks. */ + requests: z + .looseObject({ + /** @description Task support for tool-related requests. */ + tools: z + .looseObject({ + /** @description Whether the server supports task-augmented tools/call requests. */ + call: AssertObjectSchema.optional().describe('Whether the server supports task-augmented tools/call requests.') + }) + .optional() + .describe('Task support for tool-related requests.') + }) + .optional() + .describe('Specifies which request types can be augmented with tasks.') +}); + +/** + * @description A request that expects a response. + * @category JSON-RPC + */ +export const JSONRPCRequestSchema = RequestSchema.extend({ + jsonrpc: z.literal('2.0'), + id: RequestIdSchema +}) + .strict() + .describe('A request that expects a response.'); + +/** + * @description An error response that indicates that the server requires the client to provide additional information via an elicitation request. + * @internal + */ +export const URLElicitationRequiredErrorSchema = JSONRPCErrorResponseSchema.omit({ error: true }) + .extend({ + error: ErrorSchema.and( + z.object({ + code: z.any(), + data: z + .object({ + elicitations: z.array(ElicitRequestURLParamsSchema) + }) + .passthrough() + }) + ) + }) + .describe( + 'An error response that indicates that the server requires the client to provide additional information via an elicitation request.' + ); + +/* Initialization */ +/** + * @description Parameters for an `initialize` request. + * @category `initialize` + */ +export const InitializeRequestParamsSchema = RequestParamsSchema.extend({ + /** @description The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. */ + protocolVersion: z + .string() + .describe( + 'The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.' + ), + capabilities: ClientCapabilitiesSchema, + clientInfo: ImplementationSchema +}).describe('Parameters for an `initialize` request.'); + +/** + * @description This request is sent from the client to the server when it first connects, asking it to begin initialization. + * @category `initialize` + */ +export const InitializeRequestSchema = RequestSchema.extend({ + method: z.literal('initialize'), + params: InitializeRequestParamsSchema +}).describe('This request is sent from the client to the server when it first connects, asking it to begin initialization.'); + +/** + * @description A known resource that the server is capable of reading. + * @category `resources/list` + */ +export const ResourceSchema = BaseMetadataSchema.extend(IconsSchema.shape) + .extend({ + /** + * @description The URI of this resource. + * @format uri + */ + uri: z.string().describe('The URI of this resource.'), + /** @description A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. */ + description: z + .string() + .optional() + .describe( + 'A description of what this resource represents.\n\nThis can be used by clients to improve the LLM\'s understanding of available resources. It can be thought of like a "hint" to the model.' + ), + /** @description The MIME type of this resource, if known. */ + mimeType: z.string().optional().describe('The MIME type of this resource, if known.'), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. */ + size: z + .number() + .optional() + .describe( + 'The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.' + ), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('A known resource that the server is capable of reading.'); + +/** + * @description A template description for resources available on the server. + * @category `resources/templates/list` + */ +export const ResourceTemplateSchema = BaseMetadataSchema.extend(IconsSchema.shape) + .extend({ + /** + * @description A URI template (according to RFC 6570) that can be used to construct resource URIs. + * @format uri-template + */ + uriTemplate: z.string().describe('A URI template (according to RFC 6570) that can be used to construct resource URIs.'), + /** @description A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. */ + description: z + .string() + .optional() + .describe( + 'A description of what this template is for.\n\nThis can be used by clients to improve the LLM\'s understanding of available resources. It can be thought of like a "hint" to the model.' + ), + /** @description The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. */ + mimeType: z + .string() + .optional() + .describe( + 'The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.' + ), + /** @description Optional annotations for the client. */ + annotations: AnnotationsSchema.optional().describe('Optional annotations for the client.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('A template description for resources available on the server.'); + +/** + * @description A prompt or prompt template that the server offers. + * @category `prompts/list` + */ +export const PromptSchema = BaseMetadataSchema.extend(IconsSchema.shape) + .extend({ + /** @description An optional description of what this prompt provides */ + description: z.string().optional().describe('An optional description of what this prompt provides'), + /** @description A list of arguments to use for templating the prompt. */ + arguments: z.array(PromptArgumentSchema).optional().describe('A list of arguments to use for templating the prompt.'), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('A prompt or prompt template that the server offers.'); + +/** + * @description A resource that the server is capable of reading, included in a prompt or tool call result. + +Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + * @category Content + */ +export const ResourceLinkSchema = ResourceSchema.extend({ + type: z.literal('resource_link') +}).describe( + 'A resource that the server is capable of reading, included in a prompt or tool call result. Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.' +); + +/** + * @category Content + */ +export const ContentBlockSchema = z.discriminatedUnion('type', [ + TextContentSchema, + ImageContentSchema, + AudioContentSchema, + ResourceLinkSchema, + EmbeddedResourceSchema +]); + +/** + * @description The server's preferences for model selection, requested of the client during sampling. + +Because LLMs can vary along multiple dimensions, choosing the "best" model is +rarely straightforward. Different models excel in different areas—some are +faster but less capable, others are more capable but more expensive, and so +on. This interface allows servers to express their priorities across multiple +dimensions to help clients make an appropriate selection for their use case. + +These preferences are always advisory. The client MAY ignore them. It is also +up to the client to decide how to interpret these preferences and how to +balance them against other considerations. + * @category `sampling/createMessage` + */ +export const ModelPreferencesSchema = z + .object({ + /** @description Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. */ + hints: z + .array(ModelHintSchema) + .optional() + .describe( + 'Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.' + ), + /** + * @description How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + costPriority: z + .number() + .min(0) + .max(1) + .optional() + .describe( + 'How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.' + ), + /** + * @description How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + speedPriority: z + .number() + .min(0) + .max(1) + .optional() + .describe( + 'How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.' + ), + /** + * @description How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + intelligencePriority: z + .number() + .min(0) + .max(1) + .optional() + .describe( + 'How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.' + ) + }) + .describe( + 'The server\'s preferences for model selection, requested of the client during sampling. Because LLMs can vary along multiple dimensions, choosing the "best" model is rarely straightforward. Different models excel in different areas—some are faster but less capable, others are more capable but more expensive, and so on. This interface allows servers to express their priorities across multiple dimensions to help clients make an appropriate selection for their use case. These preferences are always advisory. The client MAY ignore them. It is also up to the client to decide how to interpret these preferences and how to balance them against other considerations.' + ); + +/** + * @description The result of a tool use, provided by the user back to the assistant. + * @category `sampling/createMessage` + */ +export const ToolResultContentSchema = z + .object({ + type: z.literal('tool_result'), + /** @description The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. */ + toolUseId: z + .string() + .describe('The ID of the tool use this result corresponds to.\n\nThis MUST match the ID from a previous ToolUseContent.'), + /** @description The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. */ + content: z + .array(ContentBlockSchema) + .describe( + 'The unstructured result content of the tool use.\n\nThis has the same format as CallToolResult.content and can include text, images,\naudio, resource links, and embedded resources.' + ) + .default([]), + /** @description An optional structured result object. + + If the tool defined an outputSchema, this SHOULD conform to that schema. */ + structuredContent: z + .record(z.string(), z.unknown()) + .optional() + .describe('An optional structured result object.\n\nIf the tool defined an outputSchema, this SHOULD conform to that schema.'), + /** @description Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false */ + isError: z + .boolean() + .optional() + .describe( + 'Whether the tool use resulted in an error.\n\nIf true, the content typically describes the error that occurred.\nDefault: false' + ), + /** @description Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe( + 'Optional metadata about the tool result. Clients SHOULD preserve this field when\nincluding tool results in subsequent sampling requests to enable caching optimizations.\n\nSee [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.' + ) + }) + .describe('The result of a tool use, provided by the user back to the assistant.'); + +/** + * @description Restricted schema definitions that only allow primitive types +without nested objects or arrays. + * @category `elicitation/create` + */ +export const PrimitiveSchemaDefinitionSchema = z + .union([EnumSchemaSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema]) + .describe('Restricted schema definitions that only allow primitive types\nwithout nested objects or arrays.'); + +/** + * @description The parameters for a request to elicit non-sensitive information from the user via a form in the client. + * @category `elicitation/create` + */ +export const ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.extend({ + /** @description The elicitation mode. */ + mode: z.literal('form').optional().describe('The elicitation mode.'), + /** @description The message to present to the user describing what information is being requested. */ + message: z.string().describe('The message to present to the user describing what information is being requested.'), + /** @description A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. */ + requestedSchema: z + .object({ + $schema: z.string().optional(), + type: z.literal('object'), + properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), + required: z.array(z.string()).optional() + }) + .describe('A restricted subset of JSON Schema.\nOnly top-level properties are allowed, without nesting.') +}).describe('The parameters for a request to elicit non-sensitive information from the user via a form in the client.'); + +/** + * @description The parameters for a request to elicit additional information from the user via the client. + * @category `elicitation/create` + */ +export const ElicitRequestParamsSchema = z + .union([ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema]) + .describe('The parameters for a request to elicit additional information from the user via the client.'); + +/* Client messages */ +/** @internal */ +export const ClientRequestSchema = z.union([ + PingRequestSchema, + InitializeRequestSchema, + CompleteRequestSchema, + SetLevelRequestSchema, + GetPromptRequestSchema, + ListPromptsRequestSchema, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ReadResourceRequestSchema, + SubscribeRequestSchema, + UnsubscribeRequestSchema, + CallToolRequestSchema, + ListToolsRequestSchema, + GetTaskRequestSchema, + GetTaskPayloadRequestSchema, + ListTasksRequestSchema, + CancelTaskRequestSchema +]); + +/** + * @description A request from the server to elicit additional information from the user via the client. + * @category `elicitation/create` + */ +export const ElicitRequestSchema = RequestSchema.extend({ + method: z.literal('elicitation/create'), + params: ElicitRequestParamsSchema +}).describe('A request from the server to elicit additional information from the user via the client.'); + +/** + * @description The server's response to a prompts/list request from the client. + * @category `prompts/list` + */ +export const ListPromptsResultSchema = PaginatedResultSchema.extend({ + prompts: z.array(PromptSchema) +}).describe("The server's response to a prompts/list request from the client."); + +/** + * @description The server's response to a resources/templates/list request from the client. + * @category `resources/templates/list` + */ +export const ListResourceTemplatesResultSchema = PaginatedResultSchema.extend({ + resourceTemplates: z.array(ResourceTemplateSchema) +}).describe("The server's response to a resources/templates/list request from the client."); + +/** + * @description The server's response to a resources/list request from the client. + * @category `resources/list` + */ +export const ListResourcesResultSchema = PaginatedResultSchema.extend({ + resources: z.array(ResourceSchema) +}).describe("The server's response to a resources/list request from the client."); + +/** + * @description The server's response to a tool call. + * @category `tools/call` + */ +export const CallToolResultSchema = ResultSchema.extend({ + /** @description A list of content objects that represent the unstructured result of the tool call. */ + content: z + .array(ContentBlockSchema) + .describe('A list of content objects that represent the unstructured result of the tool call.') + .default([]), + /** @description An optional JSON object that represents the structured result of the tool call. */ + structuredContent: z + .record(z.string(), z.unknown()) + .optional() + .describe('An optional JSON object that represents the structured result of the tool call.'), + /** @description Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. */ + isError: z + .boolean() + .optional() + .describe( + 'Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.' + ) +}).describe("The server's response to a tool call."); + +/** @description This file is automatically generated from the Model Context Protocol specification. + +Source: https://github.com/modelcontextprotocol/modelcontextprotocol +Pulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts +Last updated from commit: 35fa160caf287a9c48696e3ae452c0645c713669 + +DO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates. +To update this file, run: npm run fetch:spec-types */ /* JSON-RPC types */ +/** + * Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + * + * @category JSON-RPC + */ +export const JSONRPCMessageSchema = z + .union([JSONRPCRequestSchema, JSONRPCNotificationSchema, JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]) + .describe( + 'This file is automatically generated from the Model Context Protocol specification.\n\nSource: https://github.com/modelcontextprotocol/modelcontextprotocol\nPulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts\nLast updated from commit: 35fa160caf287a9c48696e3ae452c0645c713669\n\nDO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates.\nTo update this file, run: npm run fetch:spec-types' + ); + +/** + * @description Describes a message returned as part of a prompt. + +This is similar to `SamplingMessage`, but also supports the embedding of +resources from the MCP server. + * @category `prompts/get` + */ +export const PromptMessageSchema = z + .object({ + role: RoleSchema, + content: ContentBlockSchema + }) + .describe( + 'Describes a message returned as part of a prompt. This is similar to `SamplingMessage`, but also supports the embedding of resources from the MCP server.' + ); + +export const SamplingMessageContentBlockSchema = z.discriminatedUnion('type', [ + TextContentSchema, + ImageContentSchema, + AudioContentSchema, + ToolUseContentSchema, + ToolResultContentSchema +]); + +/** + * @description The server's response to a prompts/get request from the client. + * @category `prompts/get` + */ +export const GetPromptResultSchema = ResultSchema.extend({ + /** @description An optional description for the prompt. */ + description: z.string().optional().describe('An optional description for the prompt.'), + messages: z.array(PromptMessageSchema) +}).describe("The server's response to a prompts/get request from the client."); + +/** + * @description Describes a message issued to or received from an LLM API. + * @category `sampling/createMessage` + */ +export const SamplingMessageSchema = z + .object({ + role: RoleSchema, + content: z.union([SamplingMessageContentBlockSchema, z.array(SamplingMessageContentBlockSchema)]), + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta: z + .record(z.string(), z.unknown()) + .optional() + .describe('See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage.') + }) + .describe('Describes a message issued to or received from an LLM API.'); + +/* Sampling */ +/** + * @description Parameters for a `sampling/createMessage` request. + * @category `sampling/createMessage` + */ +export const CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({ + messages: z.array(SamplingMessageSchema), + /** @description The server's preferences for which model to select. The client MAY ignore these preferences. */ + modelPreferences: ModelPreferencesSchema.optional().describe( + "The server's preferences for which model to select. The client MAY ignore these preferences." + ), + /** @description An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. */ + systemPrompt: z + .string() + .optional() + .describe('An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.'), + /** @description A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. */ + includeContext: z + .enum(['none', 'thisServer', 'allServers']) + .optional() + .describe( + 'A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.\nThe client MAY ignore this request.\n\nDefault is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client\ndeclares ClientCapabilities.sampling.context. These values may be removed in future spec releases.' + ), + /** + * @TJS-type number + */ + temperature: z.number().optional(), + /** @description The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. */ + maxTokens: z + .number() + .describe( + 'The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.' + ), + stopSequences: z.array(z.string()).optional(), + /** @description Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ + metadata: z + .record(z.string(), z.any()) + .optional() + .describe('Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.'), + /** @description Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. */ + tools: z + .array(ToolSchema) + .optional() + .describe( + 'Tools that the model may use during generation.\nThe client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.' + ), + /** @description Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. */ + toolChoice: ToolChoiceSchema.optional().describe( + 'Controls how the model uses tools.\nThe client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.\nDefault is `{ mode: "auto" }`.' + ) +}).describe('Parameters for a `sampling/createMessage` request.'); + +/** + * @description The client's response to a sampling/createMessage request from the server. +The client should inform the user before returning the sampled message, to allow them +to inspect the response (human in the loop) and decide whether to allow the server to see it. + * @category `sampling/createMessage` + */ +export const CreateMessageResultSchema = ResultSchema.extend(SamplingMessageSchema.shape) + .extend({ + /** @description The name of the model that generated the message. */ + model: z.string().describe('The name of the model that generated the message.'), + /** @description The reason why sampling stopped, if known. + + Standard values: + - "endTurn": Natural end of the assistant's turn + - "stopSequence": A stop sequence was encountered + - "maxTokens": Maximum token limit was reached + - "toolUse": The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. */ + stopReason: z + .union([z.literal('endTurn'), z.literal('stopSequence'), z.literal('maxTokens'), z.literal('toolUse'), z.string()]) + .optional() + .describe( + 'The reason why sampling stopped, if known.\n\nStandard values:\n- "endTurn": Natural end of the assistant\'s turn\n- "stopSequence": A stop sequence was encountered\n- "maxTokens": Maximum token limit was reached\n- "toolUse": The model wants to use one or more tools\n\nThis field is an open string to allow for provider-specific stop reasons.' + ) + }) + .describe( + "The client's response to a sampling/createMessage request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it." + ); + +/** @internal */ +export const ClientResultSchema = z.union([ + EmptyResultSchema, + CreateMessageResultSchema, + ListRootsResultSchema, + ElicitResultSchema, + GetTaskResultSchema, + GetTaskPayloadResultSchema, + ListTasksResultSchema, + CancelTaskResultSchema +]); + +/** + * @description A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. + * @category `sampling/createMessage` + */ +export const CreateMessageRequestSchema = RequestSchema.extend({ + method: z.literal('sampling/createMessage'), + params: CreateMessageRequestParamsSchema +}).describe( + 'A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.' +); + +/** @internal */ +export const ServerResultSchema = z.union([ + EmptyResultSchema, + InitializeResultSchema, + CompleteResultSchema, + GetPromptResultSchema, + ListPromptsResultSchema, + ListResourceTemplatesResultSchema, + ListResourcesResultSchema, + ReadResourceResultSchema, + CallToolResultSchema, + ListToolsResultSchema, + GetTaskResultSchema, + GetTaskPayloadResultSchema, + ListTasksResultSchema, + CancelTaskResultSchema +]); + +/* Server messages */ +/** @internal */ +export const ServerRequestSchema = z.union([ + PingRequestSchema, + CreateMessageRequestSchema, + ListRootsRequestSchema, + ElicitRequestSchema, + GetTaskRequestSchema, + GetTaskPayloadRequestSchema, + ListTasksRequestSchema, + CancelTaskRequestSchema +]); diff --git a/src/generated/sdk.schemas.zod.test.ts b/src/generated/sdk.schemas.zod.test.ts new file mode 100644 index 000000000..4f764acac --- /dev/null +++ b/src/generated/sdk.schemas.zod.test.ts @@ -0,0 +1,633 @@ +// Generated by ts-to-zod +// Integration tests verifying schemas match TypeScript types +// Run: npm run generate:schemas +import { z } from 'zod/v4'; + +import * as spec from './sdk.types.js'; +import * as generated from './sdk.schemas.js'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function expectType(_: T) { + /* noop */ +} + +export type ProgressTokenSchemaInferredType = z.infer; + +export type CursorSchemaInferredType = z.infer; + +export type TaskMetadataSchemaInferredType = z.infer; + +export type RelatedTaskMetadataSchemaInferredType = z.infer; + +export type RequestParamsSchemaInferredType = z.infer; + +export type NotificationParamsSchemaInferredType = z.infer; + +export type NotificationSchemaInferredType = z.infer; + +export type ResultSchemaInferredType = z.infer; + +export type ErrorSchemaInferredType = z.infer; + +export type RequestIdSchemaInferredType = z.infer; + +export type RequestSchemaInferredType = z.infer; + +export type JSONRPCNotificationSchemaInferredType = z.infer; + +export type JSONRPCResultResponseSchemaInferredType = z.infer; + +export type JSONRPCErrorResponseSchemaInferredType = z.infer; + +export type EmptyResultSchemaInferredType = z.infer; + +export type CancelledNotificationParamsSchemaInferredType = z.infer; + +export type CancelledNotificationSchemaInferredType = z.infer; + +export type ClientCapabilitiesSchemaInferredType = z.infer; + +export type ServerCapabilitiesSchemaInferredType = z.infer; + +export type InitializedNotificationSchemaInferredType = z.infer; + +export type IconSchemaInferredType = z.infer; + +export type IconsSchemaInferredType = z.infer; + +export type BaseMetadataSchemaInferredType = z.infer; + +export type ImplementationSchemaInferredType = z.infer; + +export type PingRequestSchemaInferredType = z.infer; + +export type ProgressNotificationParamsSchemaInferredType = z.infer; + +export type ProgressNotificationSchemaInferredType = z.infer; + +export type PaginatedRequestParamsSchemaInferredType = z.infer; + +export type PaginatedRequestSchemaInferredType = z.infer; + +export type PaginatedResultSchemaInferredType = z.infer; + +export type ListResourcesRequestSchemaInferredType = z.infer; + +export type ListResourceTemplatesRequestSchemaInferredType = z.infer; + +export type ResourceRequestParamsSchemaInferredType = z.infer; + +export type ReadResourceRequestParamsSchemaInferredType = z.infer; + +export type ReadResourceRequestSchemaInferredType = z.infer; + +export type ResourceListChangedNotificationSchemaInferredType = z.infer; + +export type SubscribeRequestParamsSchemaInferredType = z.infer; + +export type SubscribeRequestSchemaInferredType = z.infer; + +export type UnsubscribeRequestParamsSchemaInferredType = z.infer; + +export type UnsubscribeRequestSchemaInferredType = z.infer; + +export type ResourceUpdatedNotificationParamsSchemaInferredType = z.infer; + +export type ResourceUpdatedNotificationSchemaInferredType = z.infer; + +export type ResourceContentsSchemaInferredType = z.infer; + +export type TextResourceContentsSchemaInferredType = z.infer; + +export type BlobResourceContentsSchemaInferredType = z.infer; + +export type ListPromptsRequestSchemaInferredType = z.infer; + +export type GetPromptRequestParamsSchemaInferredType = z.infer; + +export type GetPromptRequestSchemaInferredType = z.infer; + +export type PromptArgumentSchemaInferredType = z.infer; + +export type RoleSchemaInferredType = z.infer; + +export type AnnotationsSchemaInferredType = z.infer; + +export type PromptListChangedNotificationSchemaInferredType = z.infer; + +export type ListToolsRequestSchemaInferredType = z.infer; + +export type TaskAugmentedRequestParamsSchemaInferredType = z.infer; + +export type CallToolRequestParamsSchemaInferredType = z.infer; + +export type ToolListChangedNotificationSchemaInferredType = z.infer; + +export type ToolAnnotationsSchemaInferredType = z.infer; + +export type ToolExecutionSchemaInferredType = z.infer; + +export type ToolSchemaInferredType = z.infer; + +export type TaskStatusSchemaInferredType = z.infer; + +export type TaskSchemaInferredType = z.infer; + +export type CreateTaskResultSchemaInferredType = z.infer; + +export type GetTaskRequestSchemaInferredType = z.infer; + +export type GetTaskResultSchemaInferredType = z.infer; + +export type GetTaskPayloadRequestSchemaInferredType = z.infer; + +export type GetTaskPayloadResultSchemaInferredType = z.infer; + +export type CancelTaskRequestSchemaInferredType = z.infer; + +export type CancelTaskResultSchemaInferredType = z.infer; + +export type ListTasksRequestSchemaInferredType = z.infer; + +export type ListTasksResultSchemaInferredType = z.infer; + +export type TaskStatusNotificationParamsSchemaInferredType = z.infer; + +export type TaskStatusNotificationSchemaInferredType = z.infer; + +export type LoggingLevelSchemaInferredType = z.infer; + +export type SetLevelRequestParamsSchemaInferredType = z.infer; + +export type LoggingMessageNotificationParamsSchemaInferredType = z.infer; + +export type LoggingMessageNotificationSchemaInferredType = z.infer; + +export type ToolChoiceSchemaInferredType = z.infer; + +export type TextContentSchemaInferredType = z.infer; + +export type ImageContentSchemaInferredType = z.infer; + +export type AudioContentSchemaInferredType = z.infer; + +export type ToolUseContentSchemaInferredType = z.infer; + +export type EmbeddedResourceSchemaInferredType = z.infer; + +export type ModelHintSchemaInferredType = z.infer; + +export type PromptReferenceSchemaInferredType = z.infer; + +export type ResourceTemplateReferenceSchemaInferredType = z.infer; + +export type CompleteRequestParamsSchemaInferredType = z.infer; + +export type CompleteResultSchemaInferredType = z.infer; + +export type ListRootsRequestSchemaInferredType = z.infer; + +export type RootSchemaInferredType = z.infer; + +export type RootsListChangedNotificationSchemaInferredType = z.infer; + +export type ElicitRequestURLParamsSchemaInferredType = z.infer; + +export type StringSchemaSchemaInferredType = z.infer; + +export type NumberSchemaSchemaInferredType = z.infer; + +export type BooleanSchemaSchemaInferredType = z.infer; + +export type UntitledSingleSelectEnumSchemaSchemaInferredType = z.infer; + +export type TitledSingleSelectEnumSchemaSchemaInferredType = z.infer; + +export type SingleSelectEnumSchemaSchemaInferredType = z.infer; + +export type UntitledMultiSelectEnumSchemaSchemaInferredType = z.infer; + +export type TitledMultiSelectEnumSchemaSchemaInferredType = z.infer; + +export type MultiSelectEnumSchemaSchemaInferredType = z.infer; + +export type LegacyTitledEnumSchemaSchemaInferredType = z.infer; + +export type EnumSchemaSchemaInferredType = z.infer; + +export type ElicitResultSchemaInferredType = z.infer; + +export type ElicitationCompleteNotificationSchemaInferredType = z.infer; + +export type CompleteRequestSchemaInferredType = z.infer; + +export type SetLevelRequestSchemaInferredType = z.infer; + +export type CallToolRequestSchemaInferredType = z.infer; + +export type ClientNotificationSchemaInferredType = z.infer; + +export type ListRootsResultSchemaInferredType = z.infer; + +export type ServerNotificationSchemaInferredType = z.infer; + +export type InitializeResultSchemaInferredType = z.infer; + +export type ReadResourceResultSchemaInferredType = z.infer; + +export type ListToolsResultSchemaInferredType = z.infer; + +export type ClientTasksCapabilitySchemaInferredType = z.infer; + +export type ServerTasksCapabilitySchemaInferredType = z.infer; + +export type JSONRPCRequestSchemaInferredType = z.infer; + +export type URLElicitationRequiredErrorSchemaInferredType = z.infer; + +export type InitializeRequestParamsSchemaInferredType = z.infer; + +export type InitializeRequestSchemaInferredType = z.infer; + +export type ResourceSchemaInferredType = z.infer; + +export type ResourceTemplateSchemaInferredType = z.infer; + +export type PromptSchemaInferredType = z.infer; + +export type ResourceLinkSchemaInferredType = z.infer; + +export type ContentBlockSchemaInferredType = z.infer; + +export type ModelPreferencesSchemaInferredType = z.infer; + +export type ToolResultContentSchemaInferredType = z.infer; + +export type PrimitiveSchemaDefinitionSchemaInferredType = z.infer; + +export type ElicitRequestFormParamsSchemaInferredType = z.infer; + +export type ElicitRequestParamsSchemaInferredType = z.infer; + +export type ClientRequestSchemaInferredType = z.infer; + +export type ElicitRequestSchemaInferredType = z.infer; + +export type ListPromptsResultSchemaInferredType = z.infer; + +export type ListResourceTemplatesResultSchemaInferredType = z.infer; + +export type ListResourcesResultSchemaInferredType = z.infer; + +export type CallToolResultSchemaInferredType = z.infer; + +export type JSONRPCMessageSchemaInferredType = z.infer; + +export type PromptMessageSchemaInferredType = z.infer; + +export type SamplingMessageContentBlockSchemaInferredType = z.infer; + +export type GetPromptResultSchemaInferredType = z.infer; + +export type SamplingMessageSchemaInferredType = z.infer; + +export type CreateMessageRequestParamsSchemaInferredType = z.infer; + +export type CreateMessageResultSchemaInferredType = z.infer; + +export type ClientResultSchemaInferredType = z.infer; + +export type CreateMessageRequestSchemaInferredType = z.infer; + +export type ServerResultSchemaInferredType = z.infer; + +export type ServerRequestSchemaInferredType = z.infer; + +expectType({} as ProgressTokenSchemaInferredType); +expectType({} as spec.ProgressToken); +expectType({} as CursorSchemaInferredType); +expectType({} as spec.Cursor); +expectType({} as TaskMetadataSchemaInferredType); +expectType({} as spec.TaskMetadata); +expectType({} as RelatedTaskMetadataSchemaInferredType); +expectType({} as spec.RelatedTaskMetadata); +expectType({} as RequestParamsSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.RequestParams) +expectType({} as NotificationParamsSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.NotificationParams) +// Skip: schema-inferred object type incompatible with spec union type +// expectType({} as NotificationSchemaInferredType) +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.Notification) +// Skip: schema-inferred object type incompatible with spec union type +// expectType({} as ResultSchemaInferredType) +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.Result) +expectType({} as ErrorSchemaInferredType); +expectType({} as spec.Error); +expectType({} as RequestIdSchemaInferredType); +expectType({} as spec.RequestId); +// Skip: schema-inferred object type incompatible with spec union type +// expectType({} as RequestSchemaInferredType) +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.Request) +expectType({} as JSONRPCNotificationSchemaInferredType); +expectType({} as spec.JSONRPCNotification); +expectType({} as JSONRPCResultResponseSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.JSONRPCResultResponse) +expectType({} as JSONRPCErrorResponseSchemaInferredType); +expectType({} as spec.JSONRPCErrorResponse); +expectType({} as EmptyResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.EmptyResult) +expectType({} as CancelledNotificationParamsSchemaInferredType); +expectType({} as spec.CancelledNotificationParams); +expectType({} as CancelledNotificationSchemaInferredType); +expectType({} as spec.CancelledNotification); +expectType({} as ClientCapabilitiesSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ClientCapabilities) +expectType({} as ServerCapabilitiesSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ServerCapabilities) +expectType({} as InitializedNotificationSchemaInferredType); +expectType({} as spec.InitializedNotification); +expectType({} as IconSchemaInferredType); +expectType({} as spec.Icon); +expectType({} as IconsSchemaInferredType); +expectType({} as spec.Icons); +expectType({} as BaseMetadataSchemaInferredType); +expectType({} as spec.BaseMetadata); +expectType({} as ImplementationSchemaInferredType); +expectType({} as spec.Implementation); +expectType({} as PingRequestSchemaInferredType); +expectType({} as spec.PingRequest); +expectType({} as ProgressNotificationParamsSchemaInferredType); +expectType({} as spec.ProgressNotificationParams); +expectType({} as ProgressNotificationSchemaInferredType); +expectType({} as spec.ProgressNotification); +expectType({} as PaginatedRequestParamsSchemaInferredType); +expectType({} as spec.PaginatedRequestParams); +expectType({} as PaginatedRequestSchemaInferredType); +expectType({} as spec.PaginatedRequest); +expectType({} as PaginatedResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.PaginatedResult) +expectType({} as ListResourcesRequestSchemaInferredType); +expectType({} as spec.ListResourcesRequest); +expectType({} as ListResourceTemplatesRequestSchemaInferredType); +expectType({} as spec.ListResourceTemplatesRequest); +expectType({} as ResourceRequestParamsSchemaInferredType); +expectType({} as spec.ResourceRequestParams); +expectType({} as ReadResourceRequestParamsSchemaInferredType); +expectType({} as spec.ReadResourceRequestParams); +expectType({} as ReadResourceRequestSchemaInferredType); +expectType({} as spec.ReadResourceRequest); +expectType({} as ResourceListChangedNotificationSchemaInferredType); +expectType({} as spec.ResourceListChangedNotification); +expectType({} as SubscribeRequestParamsSchemaInferredType); +expectType({} as spec.SubscribeRequestParams); +expectType({} as SubscribeRequestSchemaInferredType); +expectType({} as spec.SubscribeRequest); +expectType({} as UnsubscribeRequestParamsSchemaInferredType); +expectType({} as spec.UnsubscribeRequestParams); +expectType({} as UnsubscribeRequestSchemaInferredType); +expectType({} as spec.UnsubscribeRequest); +expectType({} as ResourceUpdatedNotificationParamsSchemaInferredType); +expectType({} as spec.ResourceUpdatedNotificationParams); +expectType({} as ResourceUpdatedNotificationSchemaInferredType); +expectType({} as spec.ResourceUpdatedNotification); +expectType({} as ResourceContentsSchemaInferredType); +expectType({} as spec.ResourceContents); +expectType({} as TextResourceContentsSchemaInferredType); +expectType({} as spec.TextResourceContents); +expectType({} as BlobResourceContentsSchemaInferredType); +expectType({} as spec.BlobResourceContents); +expectType({} as ListPromptsRequestSchemaInferredType); +expectType({} as spec.ListPromptsRequest); +expectType({} as GetPromptRequestParamsSchemaInferredType); +expectType({} as spec.GetPromptRequestParams); +expectType({} as GetPromptRequestSchemaInferredType); +expectType({} as spec.GetPromptRequest); +expectType({} as PromptArgumentSchemaInferredType); +expectType({} as spec.PromptArgument); +expectType({} as RoleSchemaInferredType); +expectType({} as spec.Role); +expectType({} as AnnotationsSchemaInferredType); +expectType({} as spec.Annotations); +expectType({} as PromptListChangedNotificationSchemaInferredType); +expectType({} as spec.PromptListChangedNotification); +expectType({} as ListToolsRequestSchemaInferredType); +expectType({} as spec.ListToolsRequest); +expectType({} as TaskAugmentedRequestParamsSchemaInferredType); +expectType({} as spec.TaskAugmentedRequestParams); +expectType({} as CallToolRequestParamsSchemaInferredType); +expectType({} as spec.CallToolRequestParams); +expectType({} as ToolListChangedNotificationSchemaInferredType); +expectType({} as spec.ToolListChangedNotification); +expectType({} as ToolAnnotationsSchemaInferredType); +expectType({} as spec.ToolAnnotations); +expectType({} as ToolExecutionSchemaInferredType); +expectType({} as spec.ToolExecution); +expectType({} as ToolSchemaInferredType); +expectType({} as spec.Tool); +expectType({} as TaskStatusSchemaInferredType); +expectType({} as spec.TaskStatus); +expectType({} as TaskSchemaInferredType); +expectType({} as spec.Task); +expectType({} as CreateTaskResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.CreateTaskResult) +expectType({} as GetTaskRequestSchemaInferredType); +expectType({} as spec.GetTaskRequest); +expectType({} as GetTaskResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.GetTaskResult) +expectType({} as GetTaskPayloadRequestSchemaInferredType); +expectType({} as spec.GetTaskPayloadRequest); +expectType({} as GetTaskPayloadResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.GetTaskPayloadResult) +expectType({} as CancelTaskRequestSchemaInferredType); +expectType({} as spec.CancelTaskRequest); +expectType({} as CancelTaskResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.CancelTaskResult) +expectType({} as ListTasksRequestSchemaInferredType); +expectType({} as spec.ListTasksRequest); +expectType({} as ListTasksResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListTasksResult) +expectType({} as TaskStatusNotificationParamsSchemaInferredType); +expectType({} as spec.TaskStatusNotificationParams); +expectType({} as TaskStatusNotificationSchemaInferredType); +expectType({} as spec.TaskStatusNotification); +expectType({} as LoggingLevelSchemaInferredType); +expectType({} as spec.LoggingLevel); +expectType({} as SetLevelRequestParamsSchemaInferredType); +expectType({} as spec.SetLevelRequestParams); +expectType({} as LoggingMessageNotificationParamsSchemaInferredType); +expectType({} as spec.LoggingMessageNotificationParams); +expectType({} as LoggingMessageNotificationSchemaInferredType); +expectType({} as spec.LoggingMessageNotification); +expectType({} as ToolChoiceSchemaInferredType); +expectType({} as spec.ToolChoice); +expectType({} as TextContentSchemaInferredType); +expectType({} as spec.TextContent); +expectType({} as ImageContentSchemaInferredType); +expectType({} as spec.ImageContent); +expectType({} as AudioContentSchemaInferredType); +expectType({} as spec.AudioContent); +expectType({} as ToolUseContentSchemaInferredType); +expectType({} as spec.ToolUseContent); +expectType({} as EmbeddedResourceSchemaInferredType); +expectType({} as spec.EmbeddedResource); +expectType({} as ModelHintSchemaInferredType); +expectType({} as spec.ModelHint); +expectType({} as PromptReferenceSchemaInferredType); +expectType({} as spec.PromptReference); +expectType({} as ResourceTemplateReferenceSchemaInferredType); +expectType({} as spec.ResourceTemplateReference); +expectType({} as CompleteRequestParamsSchemaInferredType); +expectType({} as spec.CompleteRequestParams); +expectType({} as CompleteResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.CompleteResult) +expectType({} as ListRootsRequestSchemaInferredType); +expectType({} as spec.ListRootsRequest); +expectType({} as RootSchemaInferredType); +expectType({} as spec.Root); +expectType({} as RootsListChangedNotificationSchemaInferredType); +expectType({} as spec.RootsListChangedNotification); +expectType({} as ElicitRequestURLParamsSchemaInferredType); +expectType({} as spec.ElicitRequestURLParams); +expectType({} as StringSchemaSchemaInferredType); +expectType({} as spec.StringSchema); +expectType({} as NumberSchemaSchemaInferredType); +expectType({} as spec.NumberSchema); +expectType({} as BooleanSchemaSchemaInferredType); +expectType({} as spec.BooleanSchema); +expectType({} as UntitledSingleSelectEnumSchemaSchemaInferredType); +expectType({} as spec.UntitledSingleSelectEnumSchema); +expectType({} as TitledSingleSelectEnumSchemaSchemaInferredType); +expectType({} as spec.TitledSingleSelectEnumSchema); +expectType({} as SingleSelectEnumSchemaSchemaInferredType); +expectType({} as spec.SingleSelectEnumSchema); +expectType({} as UntitledMultiSelectEnumSchemaSchemaInferredType); +expectType({} as spec.UntitledMultiSelectEnumSchema); +expectType({} as TitledMultiSelectEnumSchemaSchemaInferredType); +expectType({} as spec.TitledMultiSelectEnumSchema); +expectType({} as MultiSelectEnumSchemaSchemaInferredType); +expectType({} as spec.MultiSelectEnumSchema); +expectType({} as LegacyTitledEnumSchemaSchemaInferredType); +expectType({} as spec.LegacyTitledEnumSchema); +expectType({} as EnumSchemaSchemaInferredType); +expectType({} as spec.EnumSchema); +expectType({} as ElicitResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ElicitResult) +expectType({} as ElicitationCompleteNotificationSchemaInferredType); +expectType({} as spec.ElicitationCompleteNotification); +expectType({} as CompleteRequestSchemaInferredType); +expectType({} as spec.CompleteRequest); +expectType({} as SetLevelRequestSchemaInferredType); +expectType({} as spec.SetLevelRequest); +expectType({} as CallToolRequestSchemaInferredType); +expectType({} as spec.CallToolRequest); +expectType({} as ClientNotificationSchemaInferredType); +expectType({} as spec.ClientNotification); +expectType({} as ListRootsResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListRootsResult) +expectType({} as ServerNotificationSchemaInferredType); +expectType({} as spec.ServerNotification); +expectType({} as InitializeResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.InitializeResult) +expectType({} as ReadResourceResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ReadResourceResult) +expectType({} as ListToolsResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListToolsResult) +expectType({} as ClientTasksCapabilitySchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ClientTasksCapability) +expectType({} as ServerTasksCapabilitySchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ServerTasksCapability) +expectType({} as JSONRPCRequestSchemaInferredType); +expectType({} as spec.JSONRPCRequest); +expectType({} as URLElicitationRequiredErrorSchemaInferredType); +expectType({} as spec.URLElicitationRequiredError); +expectType({} as InitializeRequestParamsSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.InitializeRequestParams) +expectType({} as InitializeRequestSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.InitializeRequest) +expectType({} as ResourceSchemaInferredType); +expectType({} as spec.Resource); +expectType({} as ResourceTemplateSchemaInferredType); +expectType({} as spec.ResourceTemplate); +expectType({} as PromptSchemaInferredType); +expectType({} as spec.Prompt); +expectType({} as ResourceLinkSchemaInferredType); +expectType({} as spec.ResourceLink); +expectType({} as ContentBlockSchemaInferredType); +expectType({} as spec.ContentBlock); +expectType({} as ModelPreferencesSchemaInferredType); +expectType({} as spec.ModelPreferences); +expectType({} as ToolResultContentSchemaInferredType); +expectType({} as spec.ToolResultContent); +expectType({} as PrimitiveSchemaDefinitionSchemaInferredType); +expectType({} as spec.PrimitiveSchemaDefinition); +expectType({} as ElicitRequestFormParamsSchemaInferredType); +expectType({} as spec.ElicitRequestFormParams); +expectType({} as ElicitRequestParamsSchemaInferredType); +expectType({} as spec.ElicitRequestParams); +expectType({} as ClientRequestSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ClientRequest) +expectType({} as ElicitRequestSchemaInferredType); +expectType({} as spec.ElicitRequest); +expectType({} as ListPromptsResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListPromptsResult) +expectType({} as ListResourceTemplatesResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListResourceTemplatesResult) +expectType({} as ListResourcesResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.ListResourcesResult) +expectType({} as CallToolResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.CallToolResult) +expectType({} as JSONRPCMessageSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.JSONRPCMessage) +expectType({} as PromptMessageSchemaInferredType); +expectType({} as spec.PromptMessage); +expectType({} as SamplingMessageContentBlockSchemaInferredType); +expectType({} as spec.SamplingMessageContentBlock); +expectType({} as GetPromptResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.GetPromptResult) +expectType({} as SamplingMessageSchemaInferredType); +expectType({} as spec.SamplingMessage); +expectType({} as CreateMessageRequestParamsSchemaInferredType); +expectType({} as spec.CreateMessageRequestParams); +expectType({} as CreateMessageResultSchemaInferredType); +// Skip: passthrough/looseObject index signature incompatible with clean spec interface +// expectType({} as spec.CreateMessageResult) +expectType({} as ClientResultSchemaInferredType); +expectType({} as spec.ClientResult); +expectType({} as CreateMessageRequestSchemaInferredType); +expectType({} as spec.CreateMessageRequest); +expectType({} as ServerResultSchemaInferredType); +expectType({} as spec.ServerResult); +expectType({} as ServerRequestSchemaInferredType); +expectType({} as spec.ServerRequest); diff --git a/src/generated/sdk.types.ts b/src/generated/sdk.types.ts new file mode 100644 index 000000000..c2474fcb9 --- /dev/null +++ b/src/generated/sdk.types.ts @@ -0,0 +1,2144 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ +/** + * SDK-compatible types generated from spec.types.ts + * + * This file is auto-generated by scripts/generate-schemas.ts + * DO NOT EDIT MANUALLY + * + * Transformations applied: + * - `extends JSONRPCRequest` → `extends Request` + * - `extends JSONRPCNotification` → `extends Notification` + * - All index signature patterns removed (enables TypeScript union narrowing) + * + * Note: Schemas use .passthrough() for runtime extensibility, so types + * don't need index signatures. This separation allows clean types for + * TypeScript while maintaining runtime flexibility. + */ + +/** + * Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + * + * @category JSON-RPC + */ +export type JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse; + +/** @internal */ +export const LATEST_PROTOCOL_VERSION = 'DRAFT-2026-v1'; +/** @internal */ +export const JSONRPC_VERSION = '2.0'; + +/** + * @description A progress token, used to associate progress notifications with the original request. + * @category Common Types + */ +export type ProgressToken = string | number; + +/** + * @description An opaque token used to represent a cursor for pagination. + * @category Common Types + */ +export type Cursor = string; + +/** + * @description Common params for any task-augmented request. + * @internal + */ +export interface TaskAugmentedRequestParams extends RequestParams { + /** @description If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. */ + task?: TaskMetadata; +} +/** + * @description Common params for any request. + * @internal + */ +export interface RequestParams { + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { + /** @description If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. */ + progressToken?: ProgressToken; + /** @description If specified, this request is related to the provided task. */ + 'io.modelcontextprotocol/related-task'?: RelatedTaskMetadata; + [key: string]: unknown; + }; +} + +/** @internal */ +export interface Request { + method: string; + // Allow unofficial extensions of `Request.params` without impacting `RequestParams`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: RequestParams & { [key: string]: any }; +} + +/** Union of all MCP request types for type narrowing. */ +export type McpRequest = + | CallToolRequest + | CancelTaskRequest + | CompleteRequest + | CreateMessageRequest + | ElicitRequest + | GetPromptRequest + | GetTaskPayloadRequest + | GetTaskRequest + | InitializeRequest + | ListPromptsRequest + | ListResourceTemplatesRequest + | ListResourcesRequest + | ListRootsRequest + | ListTasksRequest + | ListToolsRequest + | PingRequest + | ReadResourceRequest + | SetLevelRequest + | SubscribeRequest + | UnsubscribeRequest; + +/** @internal */ +export interface NotificationParams { + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** @internal */ +export interface Notification { + method: string; + // Allow unofficial extensions of `Notification.params` without impacting `NotificationParams`. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: NotificationParams & { [key: string]: any }; +} + +/** Union of all MCP notification types for type narrowing. */ +export type McpNotification = + | CancelledNotification + | ElicitationCompleteNotification + | InitializedNotification + | LoggingMessageNotification + | ProgressNotification + | PromptListChangedNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | RootsListChangedNotification + | TaskStatusNotification + | ToolListChangedNotification; + +/** + * @category Common Types + */ +export interface Result { + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** Union of all MCP result types for type narrowing. */ +export type McpResult = + | CallToolResult + | CancelTaskResult + | CompleteResult + | CreateMessageResult + | CreateTaskResult + | ElicitResult + | EmptyResult + | GetPromptResult + | GetTaskPayloadResult + | GetTaskResult + | InitializeResult + | ListPromptsResult + | ListResourceTemplatesResult + | ListResourcesResult + | ListRootsResult + | ListTasksResult + | ListToolsResult + | ReadResourceResult; + +/** + * @category Common Types + */ +export interface Error { + /** @description The error type that occurred. */ + code: number; + /** @description A short description of the error. The message SHOULD be limited to a concise single sentence. */ + message: string; + /** @description Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). */ + data?: unknown; +} + +/** + * @description A uniquely identifying ID for a request in JSON-RPC. + * @category Common Types + */ +export type RequestId = string | number; + +/** + * @description A request that expects a response. + * @category JSON-RPC + */ +export interface JSONRPCRequest extends Request { + jsonrpc: typeof JSONRPC_VERSION; + id: RequestId; +} + +/** + * @description A notification which does not expect a response. + * @category JSON-RPC + */ +export interface JSONRPCNotification extends Notification { + jsonrpc: typeof JSONRPC_VERSION; +} + +/** + * @description A successful (non-error) response to a request. + * @category JSON-RPC + */ +export interface JSONRPCResultResponse { + jsonrpc: typeof JSONRPC_VERSION; + id: RequestId; + result: Result; +} + +/** + * @description A response to a request that indicates an error occurred. + * @category JSON-RPC + */ +export interface JSONRPCErrorResponse { + jsonrpc: typeof JSONRPC_VERSION; + id?: RequestId; + error: Error; +} + +// Standard JSON-RPC error codes +export const PARSE_ERROR = -32700; +export const INVALID_REQUEST = -32600; +export const METHOD_NOT_FOUND = -32601; +export const INVALID_PARAMS = -32602; +export const INTERNAL_ERROR = -32603; + +// Implementation-specific JSON-RPC error codes [-32000, -32099] +/** @internal */ +export const URL_ELICITATION_REQUIRED = -32042; + +/** + * @description An error response that indicates that the server requires the client to provide additional information via an elicitation request. + * @internal + */ +export interface URLElicitationRequiredError extends Omit { + error: Error & { + code: typeof URL_ELICITATION_REQUIRED; + data: { + elicitations: ElicitRequestURLParams[]; + [key: string]: unknown; + }; + }; +} + +/* Empty result */ +/** + * @description A response that indicates success but carries no data. + * @category Common Types + */ +export type EmptyResult = Result; + +/* Cancellation */ +/** + * @description Parameters for a `notifications/cancelled` notification. + * @category `notifications/cancelled` + */ +export interface CancelledNotificationParams extends NotificationParams { + /** @description The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). */ + requestId?: RequestId; + + /** @description An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. */ + reason?: string; +} + +/** + * @description This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + +The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + +This notification indicates that the result will be unused, so any associated processing SHOULD cease. + +A client MUST NOT attempt to cancel its `initialize` request. + +For task cancellation, use the `tasks/cancel` request instead of this notification. + * @category `notifications/cancelled` + */ +export interface CancelledNotification extends Notification { + method: 'notifications/cancelled'; + params: CancelledNotificationParams; +} + +/* Initialization */ +/** + * @description Parameters for an `initialize` request. + * @category `initialize` + */ +export interface InitializeRequestParams extends RequestParams { + /** @description The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. */ + protocolVersion: string; + capabilities: ClientCapabilities; + clientInfo: Implementation; +} + +/** + * @description This request is sent from the client to the server when it first connects, asking it to begin initialization. + * @category `initialize` + */ +export interface InitializeRequest extends Request { + method: 'initialize'; + params: InitializeRequestParams; +} + +/** + * @description After receiving an initialize request from the client, the server sends this response. + * @category `initialize` + */ +export interface InitializeResult extends Result { + /** @description The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. */ + protocolVersion: string; + capabilities: ServerCapabilities; + serverInfo: Implementation; + + /** @description Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. */ + instructions?: string; +} + +/** + * @description This notification is sent from the client to the server after initialization has finished. + * @category `notifications/initialized` + */ +export interface InitializedNotification extends Notification { + method: 'notifications/initialized'; + params?: NotificationParams; +} + +/** + * @description Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. + * @category `initialize` + */ +export interface ClientCapabilities { + /** @description Experimental, non-standard capabilities that the client supports. */ + experimental?: { [key: string]: object }; + /** @description Present if the client supports listing roots. */ + roots?: { + /** @description Whether the client supports notifications for changes to the roots list. */ + listChanged?: boolean; + }; + /** @description Present if the client supports sampling from an LLM. */ + sampling?: { + /** @description Whether the client supports context inclusion via includeContext parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). */ + context?: object; + /** @description Whether the client supports tool use via tools and toolChoice parameters. */ + tools?: object; + }; + /** @description Present if the client supports elicitation from the server. */ + elicitation?: { form?: { applyDefaults?: boolean; [key: string]: unknown }; url?: object }; + + /** @description Present if the client supports task-augmented requests. */ + tasks?: { + /** @description Whether this client supports tasks/list. */ + list?: object; + /** @description Whether this client supports tasks/cancel. */ + cancel?: object; + /** @description Specifies which request types can be augmented with tasks. */ + requests?: { + /** @description Task support for sampling-related requests. */ + sampling?: { + /** @description Whether the client supports task-augmented sampling/createMessage requests. */ + createMessage?: object; + }; + /** @description Task support for elicitation-related requests. */ + elicitation?: { + /** @description Whether the client supports task-augmented elicitation/create requests. */ + create?: object; + }; + }; + }; +} + +/** + * @description Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. + * @category `initialize` + */ +export interface ServerCapabilities { + /** @description Experimental, non-standard capabilities that the server supports. */ + experimental?: { [key: string]: object }; + /** @description Present if the server supports sending log messages to the client. */ + logging?: object; + /** @description Present if the server supports argument autocompletion suggestions. */ + completions?: object; + /** @description Present if the server offers any prompt templates. */ + prompts?: { + /** @description Whether this server supports notifications for changes to the prompt list. */ + listChanged?: boolean; + }; + /** @description Present if the server offers any resources to read. */ + resources?: { + /** @description Whether this server supports subscribing to resource updates. */ + subscribe?: boolean; + /** @description Whether this server supports notifications for changes to the resource list. */ + listChanged?: boolean; + }; + /** @description Present if the server offers any tools to call. */ + tools?: { + /** @description Whether this server supports notifications for changes to the tool list. */ + listChanged?: boolean; + }; + /** @description Present if the server supports task-augmented requests. */ + tasks?: { + /** @description Whether this server supports tasks/list. */ + list?: object; + /** @description Whether this server supports tasks/cancel. */ + cancel?: object; + /** @description Specifies which request types can be augmented with tasks. */ + requests?: { + /** @description Task support for tool-related requests. */ + tools?: { + /** @description Whether the server supports task-augmented tools/call requests. */ + call?: object; + }; + }; + }; +} + +/** + * @description An optionally-sized icon that can be displayed in a user interface. + * @category Common Types + */ +export interface Icon { + /** + * @description A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD takes steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + * @format uri + */ + src: string; + + /** @description Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. */ + mimeType?: string; + + /** @description Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. */ + sizes?: string[]; + + /** @description Optional specifier for the theme this icon is designed for. `light` indicates + the icon is designed to be used with a light background, and `dark` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. */ + theme?: 'light' | 'dark'; +} + +/** + * @description Base interface to add `icons` property. + * @internal + */ +export interface Icons { + /** @description Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) */ + icons?: Icon[]; +} + +/** + * @description Base interface for metadata with name (identifier) and title (display name) properties. + * @internal + */ +export interface BaseMetadata { + /** @description Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). */ + name: string; + + /** @description Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). */ + title?: string; +} + +/** + * @description Describes the MCP implementation. + * @category `initialize` + */ +export interface Implementation extends BaseMetadata, Icons { + version: string; + + /** @description An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. */ + description?: string; + + /** + * @description An optional URL of the website for this implementation. + * @format uri + */ + websiteUrl?: string; +} + +/* Ping */ +/** + * @description A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. + * @category `ping` + */ +export interface PingRequest extends Request { + method: 'ping'; + params?: RequestParams; +} + +/* Progress notifications */ + +/** + * @description Parameters for a `notifications/progress` notification. + * @category `notifications/progress` + */ +export interface ProgressNotificationParams extends NotificationParams { + /** @description The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. */ + progressToken: ProgressToken; + /** + * @description The progress thus far. This should increase every time progress is made, even if the total is unknown. + * @TJS-type number + */ + progress: number; + /** + * @description Total number of items to process (or total progress required), if known. + * @TJS-type number + */ + total?: number; + /** @description An optional message describing the current progress. */ + message?: string; +} + +/** + * @description An out-of-band notification used to inform the receiver of a progress update for a long-running request. + * @category `notifications/progress` + */ +export interface ProgressNotification extends Notification { + method: 'notifications/progress'; + params: ProgressNotificationParams; +} + +/* Pagination */ +/** + * @description Common parameters for paginated requests. + * @internal + */ +export interface PaginatedRequestParams extends RequestParams { + /** @description An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. */ + cursor?: Cursor; +} + +/** @internal */ +export interface PaginatedRequest extends Request { + params?: PaginatedRequestParams; +} + +/** @internal */ +export interface PaginatedResult extends Result { + /** @description An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. */ + nextCursor?: Cursor; +} + +/* Resources */ +/** + * @description Sent from the client to request a list of resources the server has. + * @category `resources/list` + */ +export interface ListResourcesRequest extends PaginatedRequest { + method: 'resources/list'; +} + +/** + * @description The server's response to a resources/list request from the client. + * @category `resources/list` + */ +export interface ListResourcesResult extends PaginatedResult { + resources: Resource[]; +} + +/** + * @description Sent from the client to request a list of resource templates the server has. + * @category `resources/templates/list` + */ +export interface ListResourceTemplatesRequest extends PaginatedRequest { + method: 'resources/templates/list'; +} + +/** + * @description The server's response to a resources/templates/list request from the client. + * @category `resources/templates/list` + */ +export interface ListResourceTemplatesResult extends PaginatedResult { + resourceTemplates: ResourceTemplate[]; +} + +/** + * @description Common parameters when working with resources. + * @internal + */ +export interface ResourceRequestParams extends RequestParams { + /** + * @description The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + * @format uri + */ + uri: string; +} + +/** + * @description Parameters for a `resources/read` request. + * @category `resources/read` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface ReadResourceRequestParams extends ResourceRequestParams {} + +/** + * @description Sent from the client to the server, to read a specific resource URI. + * @category `resources/read` + */ +export interface ReadResourceRequest extends Request { + method: 'resources/read'; + params: ReadResourceRequestParams; +} + +/** + * @description The server's response to a resources/read request from the client. + * @category `resources/read` + */ +export interface ReadResourceResult extends Result { + contents: (TextResourceContents | BlobResourceContents)[]; +} + +/** + * @description An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/resources/list_changed` + */ +export interface ResourceListChangedNotification extends Notification { + method: 'notifications/resources/list_changed'; + params?: NotificationParams; +} + +/** + * @description Parameters for a `resources/subscribe` request. + * @category `resources/subscribe` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface SubscribeRequestParams extends ResourceRequestParams {} + +/** + * @description Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. + * @category `resources/subscribe` + */ +export interface SubscribeRequest extends Request { + method: 'resources/subscribe'; + params: SubscribeRequestParams; +} + +/** + * @description Parameters for a `resources/unsubscribe` request. + * @category `resources/unsubscribe` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UnsubscribeRequestParams extends ResourceRequestParams {} + +/** + * @description Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. + * @category `resources/unsubscribe` + */ +export interface UnsubscribeRequest extends Request { + method: 'resources/unsubscribe'; + params: UnsubscribeRequestParams; +} + +/** + * @description Parameters for a `notifications/resources/updated` notification. + * @category `notifications/resources/updated` + */ +export interface ResourceUpdatedNotificationParams extends NotificationParams { + /** + * @description The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + * @format uri + */ + uri: string; +} + +/** + * @description A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. + * @category `notifications/resources/updated` + */ +export interface ResourceUpdatedNotification extends Notification { + method: 'notifications/resources/updated'; + params: ResourceUpdatedNotificationParams; +} + +/** + * @description A known resource that the server is capable of reading. + * @category `resources/list` + */ +export interface Resource extends BaseMetadata, Icons { + /** + * @description The URI of this resource. + * @format uri + */ + uri: string; + + /** @description A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. */ + description?: string; + + /** @description The MIME type of this resource, if known. */ + mimeType?: string; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. */ + size?: number; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description A template description for resources available on the server. + * @category `resources/templates/list` + */ +export interface ResourceTemplate extends BaseMetadata, Icons { + /** + * @description A URI template (according to RFC 6570) that can be used to construct resource URIs. + * @format uri-template + */ + uriTemplate: string; + + /** @description A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. */ + description?: string; + + /** @description The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. */ + mimeType?: string; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description The contents of a specific resource or sub-resource. + * @internal + */ +export interface ResourceContents { + /** + * @description The URI of this resource. + * @format uri + */ + uri: string; + /** @description The MIME type of this resource, if known. */ + mimeType?: string; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @category Content + */ +export interface TextResourceContents extends ResourceContents { + /** @description The text of the item. This must only be set if the item can actually be represented as text (not binary data). */ + text: string; +} + +/** + * @category Content + */ +export interface BlobResourceContents extends ResourceContents { + /** + * @description A base64-encoded string representing the binary data of the item. + * @format byte + */ + blob: string; +} + +/* Prompts */ +/** + * @description Sent from the client to request a list of prompts and prompt templates the server has. + * @category `prompts/list` + */ +export interface ListPromptsRequest extends PaginatedRequest { + method: 'prompts/list'; +} + +/** + * @description The server's response to a prompts/list request from the client. + * @category `prompts/list` + */ +export interface ListPromptsResult extends PaginatedResult { + prompts: Prompt[]; +} + +/** + * @description Parameters for a `prompts/get` request. + * @category `prompts/get` + */ +export interface GetPromptRequestParams extends RequestParams { + /** @description The name of the prompt or prompt template. */ + name: string; + /** @description Arguments to use for templating the prompt. */ + arguments?: { [key: string]: string }; +} + +/** + * @description Used by the client to get a prompt provided by the server. + * @category `prompts/get` + */ +export interface GetPromptRequest extends Request { + method: 'prompts/get'; + params: GetPromptRequestParams; +} + +/** + * @description The server's response to a prompts/get request from the client. + * @category `prompts/get` + */ +export interface GetPromptResult extends Result { + /** @description An optional description for the prompt. */ + description?: string; + messages: PromptMessage[]; +} + +/** + * @description A prompt or prompt template that the server offers. + * @category `prompts/list` + */ +export interface Prompt extends BaseMetadata, Icons { + /** @description An optional description of what this prompt provides */ + description?: string; + + /** @description A list of arguments to use for templating the prompt. */ + arguments?: PromptArgument[]; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description Describes an argument that a prompt can accept. + * @category `prompts/list` + */ +export interface PromptArgument extends BaseMetadata { + /** @description A human-readable description of the argument. */ + description?: string; + /** @description Whether this argument must be provided. */ + required?: boolean; +} + +/** + * @description The sender or recipient of messages and data in a conversation. + * @category Common Types + */ +export type Role = 'user' | 'assistant'; + +/** + * @description Describes a message returned as part of a prompt. + +This is similar to `SamplingMessage`, but also supports the embedding of +resources from the MCP server. + * @category `prompts/get` + */ +export interface PromptMessage { + role: Role; + content: ContentBlock; +} + +/** + * @description A resource that the server is capable of reading, included in a prompt or tool call result. + +Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + * @category Content + */ +export interface ResourceLink extends Resource { + type: 'resource_link'; +} + +/** + * @description The contents of a resource, embedded into a prompt or tool call result. + +It is up to the client how best to render embedded resources for the benefit +of the LLM and/or the user. + * @category Content + */ +export interface EmbeddedResource { + type: 'resource'; + resource: TextResourceContents | BlobResourceContents; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} +/** + * @description An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/prompts/list_changed` + */ +export interface PromptListChangedNotification extends Notification { + method: 'notifications/prompts/list_changed'; + params?: NotificationParams; +} + +/* Tools */ +/** + * @description Sent from the client to request a list of tools the server has. + * @category `tools/list` + */ +export interface ListToolsRequest extends PaginatedRequest { + method: 'tools/list'; +} + +/** + * @description The server's response to a tools/list request from the client. + * @category `tools/list` + */ +export interface ListToolsResult extends PaginatedResult { + tools: Tool[]; +} + +/** + * @description The server's response to a tool call. + * @category `tools/call` + */ +export interface CallToolResult extends Result { + /** @description A list of content objects that represent the unstructured result of the tool call. */ + content: ContentBlock[]; + + /** @description An optional JSON object that represents the structured result of the tool call. */ + structuredContent?: { [key: string]: unknown }; + + /** @description Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. */ + isError?: boolean; +} + +/** + * @description Parameters for a `tools/call` request. + * @category `tools/call` + */ +export interface CallToolRequestParams extends TaskAugmentedRequestParams { + /** @description The name of the tool. */ + name: string; + /** @description Arguments to use for the tool call. */ + arguments?: { [key: string]: unknown }; +} + +/** + * @description Used by the client to invoke a tool provided by the server. + * @category `tools/call` + */ +export interface CallToolRequest extends Request { + method: 'tools/call'; + params: CallToolRequestParams; +} + +/** + * @description An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. + * @category `notifications/tools/list_changed` + */ +export interface ToolListChangedNotification extends Notification { + method: 'notifications/tools/list_changed'; + params?: NotificationParams; +} + +/** + * @description Additional properties describing a Tool to clients. + +NOTE: all properties in ToolAnnotations are **hints**. +They are not guaranteed to provide a faithful description of +tool behavior (including descriptive properties like `title`). + +Clients should never make tool use decisions based on ToolAnnotations +received from untrusted servers. + * @category `tools/list` + */ +export interface ToolAnnotations { + /** @description A human-readable title for the tool. */ + title?: string; + + /** @description If true, the tool does not modify its environment. + + Default: false */ + readOnlyHint?: boolean; + + /** @description If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true */ + destructiveHint?: boolean; + + /** @description If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false */ + idempotentHint?: boolean; + + /** @description If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true */ + openWorldHint?: boolean; +} + +/** + * @description Execution-related properties for a tool. + * @category `tools/list` + */ +export interface ToolExecution { + /** @description Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" */ + taskSupport?: 'forbidden' | 'optional' | 'required'; +} + +/** + * @description Definition for a tool the client can call. + * @category `tools/list` + */ +export interface Tool extends BaseMetadata, Icons { + /** @description A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. */ + description?: string; + + /** @description A JSON Schema object defining the expected parameters for the tool. */ + inputSchema: { + $schema?: string; + type: 'object'; + properties?: { [key: string]: object }; + required?: string[]; + }; + + /** @description Execution-related properties for this tool. */ + execution?: ToolExecution; + + /** @description An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. */ + outputSchema?: { + $schema?: string; + type: 'object'; + properties?: { [key: string]: object }; + required?: string[]; + }; + + /** @description Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. */ + annotations?: ToolAnnotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/* Tasks */ + +/** + * @description The status of a task. + * @category `tasks` + */ +export type TaskStatus = + | 'working' // The request is currently being processed + | 'input_required' // The task is waiting for input (e.g., elicitation or sampling) + | 'completed' // The request completed successfully and results are available + | 'failed' // The associated request did not complete successfully. For tool calls specifically, this includes cases where the tool call result has `isError` set to true. + | 'cancelled'; // The request was cancelled before completion + +/** + * @description Metadata for augmenting a request with task execution. +Include this in the `task` field of the request parameters. + * @category `tasks` + */ +export interface TaskMetadata { + /** @description Requested duration in milliseconds to retain task from creation. */ + ttl?: number; +} + +/** + * @description Metadata for associating messages with a task. +Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + * @category `tasks` + */ +export interface RelatedTaskMetadata { + /** @description The task identifier this message is associated with. */ + taskId: string; +} + +/** + * @description Data associated with a task. + * @category `tasks` + */ +export interface Task { + /** @description The task identifier. */ + taskId: string; + + /** @description Current task state. */ + status: TaskStatus; + + /** @description Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) */ + statusMessage?: string; + + /** @description ISO 8601 timestamp when the task was created. */ + createdAt: string; + + /** @description ISO 8601 timestamp when the task was last updated. */ + lastUpdatedAt: string; + + /** @description Actual retention duration from creation in milliseconds, null for unlimited. */ + ttl: number | null; + + /** @description Suggested polling interval in milliseconds. */ + pollInterval?: number; +} + +/** + * @description A response to a task-augmented request. + * @category `tasks` + */ +export interface CreateTaskResult extends Result { + task: Task; +} + +/** + * @description A request to retrieve the state of a task. + * @category `tasks/get` + */ +export interface GetTaskRequest extends Request { + method: 'tasks/get'; + params: { + /** @description The task identifier to query. */ + taskId: string; + }; +} + +/** + * @description The response to a tasks/get request. + * @category `tasks/get` + */ +export type GetTaskResult = Result & Task; + +/** + * @description A request to retrieve the result of a completed task. + * @category `tasks/result` + */ +export interface GetTaskPayloadRequest extends Request { + method: 'tasks/result'; + params: { + /** @description The task identifier to retrieve results for. */ + taskId: string; + }; +} + +/** + * @description The response to a tasks/result request. +The structure matches the result type of the original request. +For example, a tools/call task would return the CallToolResult structure. + * @category `tasks/result` + */ +export interface GetTaskPayloadResult extends Result {} + +/** + * @description A request to cancel a task. + * @category `tasks/cancel` + */ +export interface CancelTaskRequest extends Request { + method: 'tasks/cancel'; + params: { + /** @description The task identifier to cancel. */ + taskId: string; + }; +} + +/** + * @description The response to a tasks/cancel request. + * @category `tasks/cancel` + */ +export type CancelTaskResult = Result & Task; + +/** + * @description A request to retrieve a list of tasks. + * @category `tasks/list` + */ +export interface ListTasksRequest extends PaginatedRequest { + method: 'tasks/list'; +} + +/** + * @description The response to a tasks/list request. + * @category `tasks/list` + */ +export interface ListTasksResult extends PaginatedResult { + tasks: Task[]; +} + +/** + * @description Parameters for a `notifications/tasks/status` notification. + * @category `notifications/tasks/status` + */ +export type TaskStatusNotificationParams = NotificationParams & Task; + +/** + * @description An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications. + * @category `notifications/tasks/status` + */ +export interface TaskStatusNotification extends Notification { + method: 'notifications/tasks/status'; + params: TaskStatusNotificationParams; +} + +/* Logging */ + +/** + * @description Parameters for a `logging/setLevel` request. + * @category `logging/setLevel` + */ +export interface SetLevelRequestParams extends RequestParams { + /** @description The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. */ + level: LoggingLevel; +} + +/** + * @description A request from the client to the server, to enable or adjust logging. + * @category `logging/setLevel` + */ +export interface SetLevelRequest extends Request { + method: 'logging/setLevel'; + params: SetLevelRequestParams; +} + +/** + * @description Parameters for a `notifications/message` notification. + * @category `notifications/message` + */ +export interface LoggingMessageNotificationParams extends NotificationParams { + /** @description The severity of this log message. */ + level: LoggingLevel; + /** @description An optional name of the logger issuing this message. */ + logger?: string; + /** @description The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. */ + data: unknown; +} + +/** + * @description JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically. + * @category `notifications/message` + */ +export interface LoggingMessageNotification extends Notification { + method: 'notifications/message'; + params: LoggingMessageNotificationParams; +} + +/** + * @description The severity of a log message. + +These map to syslog message severities, as specified in RFC-5424: +https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + * @category Common Types + */ +export type LoggingLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency'; + +/* Sampling */ +/** + * @description Parameters for a `sampling/createMessage` request. + * @category `sampling/createMessage` + */ +export interface CreateMessageRequestParams extends TaskAugmentedRequestParams { + messages: SamplingMessage[]; + /** @description The server's preferences for which model to select. The client MAY ignore these preferences. */ + modelPreferences?: ModelPreferences; + /** @description An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. */ + systemPrompt?: string; + /** @description A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. */ + includeContext?: 'none' | 'thisServer' | 'allServers'; + /** + * @TJS-type number + */ + temperature?: number; + /** @description The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. */ + maxTokens: number; + stopSequences?: string[]; + /** @description Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ + metadata?: object; + /** @description Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. */ + tools?: Tool[]; + /** @description Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. */ + toolChoice?: ToolChoice; +} + +/** + * @description Controls tool selection behavior for sampling requests. + * @category `sampling/createMessage` + */ +export interface ToolChoice { + /** @description Controls the tool use ability of the model: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model MUST NOT use any tools */ + mode?: 'auto' | 'required' | 'none'; +} + +/** + * @description A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. + * @category `sampling/createMessage` + */ +export interface CreateMessageRequest extends Request { + method: 'sampling/createMessage'; + params: CreateMessageRequestParams; +} + +/** + * @description The client's response to a sampling/createMessage request from the server. +The client should inform the user before returning the sampled message, to allow them +to inspect the response (human in the loop) and decide whether to allow the server to see it. + * @category `sampling/createMessage` + */ +export interface CreateMessageResult extends Result, SamplingMessage { + /** @description The name of the model that generated the message. */ + model: string; + + /** @description The reason why sampling stopped, if known. + + Standard values: + - "endTurn": Natural end of the assistant's turn + - "stopSequence": A stop sequence was encountered + - "maxTokens": Maximum token limit was reached + - "toolUse": The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. */ + stopReason?: 'endTurn' | 'stopSequence' | 'maxTokens' | 'toolUse' | string; +} + +/** + * @description Describes a message issued to or received from an LLM API. + * @category `sampling/createMessage` + */ +export interface SamplingMessage { + role: Role; + content: SamplingMessageContentBlock | SamplingMessageContentBlock[]; + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} +export type SamplingMessageContentBlock = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent; + +/** + * @description Optional annotations for the client. The client can use annotations to inform how objects are used or displayed + * @category Common Types + */ +export interface Annotations { + /** @description Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). */ + audience?: Role[]; + + /** + * @description Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + priority?: number; + + /** @description The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. */ + lastModified?: string; +} + +/** + * @category Content + */ +export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; + +/** + * @description Text provided to or from an LLM. + * @category Content + */ +export interface TextContent { + type: 'text'; + + /** @description The text content of the message. */ + text: string; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description An image provided to or from an LLM. + * @category Content + */ +export interface ImageContent { + type: 'image'; + + /** + * @description The base64-encoded image data. + * @format byte + */ + data: string; + + /** @description The MIME type of the image. Different providers may support different image types. */ + mimeType: string; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description Audio provided to or from an LLM. + * @category Content + */ +export interface AudioContent { + type: 'audio'; + + /** + * @description The base64-encoded audio data. + * @format byte + */ + data: string; + + /** @description The MIME type of the audio. Different providers may support different audio types. */ + mimeType: string; + + /** @description Optional annotations for the client. */ + annotations?: Annotations; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description A request from the assistant to call a tool. + * @category `sampling/createMessage` + */ +export interface ToolUseContent { + type: 'tool_use'; + + /** @description A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. */ + id: string; + + /** @description The name of the tool to call. */ + name: string; + + /** @description The arguments to pass to the tool, conforming to the tool's input schema. */ + input: { [key: string]: unknown }; + + /** @description Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description The result of a tool use, provided by the user back to the assistant. + * @category `sampling/createMessage` + */ +export interface ToolResultContent { + type: 'tool_result'; + + /** @description The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. */ + toolUseId: string; + + /** @description The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. */ + content: ContentBlock[]; + + /** @description An optional structured result object. + + If the tool defined an outputSchema, this SHOULD conform to that schema. */ + structuredContent?: { [key: string]: unknown }; + + /** @description Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false */ + isError?: boolean; + + /** @description Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description The server's preferences for model selection, requested of the client during sampling. + +Because LLMs can vary along multiple dimensions, choosing the "best" model is +rarely straightforward. Different models excel in different areas—some are +faster but less capable, others are more capable but more expensive, and so +on. This interface allows servers to express their priorities across multiple +dimensions to help clients make an appropriate selection for their use case. + +These preferences are always advisory. The client MAY ignore them. It is also +up to the client to decide how to interpret these preferences and how to +balance them against other considerations. + * @category `sampling/createMessage` + */ +export interface ModelPreferences { + /** @description Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. */ + hints?: ModelHint[]; + + /** + * @description How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + costPriority?: number; + + /** + * @description How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + speedPriority?: number; + + /** + * @description How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + * @TJS-type number + * + * @minimum 0 + * + * @maximum 1 + */ + intelligencePriority?: number; +} + +/** + * @description Hints to use for model selection. + +Keys not declared here are currently left unspecified by the spec and are up +to the client to interpret. + * @category `sampling/createMessage` + */ +export interface ModelHint { + /** @description A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` */ + name?: string; +} + +/* Autocomplete */ +/** + * @description Parameters for a `completion/complete` request. + * @category `completion/complete` + */ +export interface CompleteRequestParams extends RequestParams { + ref: PromptReference | ResourceTemplateReference; + /** @description The argument's information */ + argument: { + /** @description The name of the argument */ + name: string; + /** @description The value of the argument to use for completion matching. */ + value: string; + }; + + /** @description Additional, optional context for completions */ + context?: { + /** @description Previously-resolved variables in a URI template or prompt. */ + arguments?: { [key: string]: string }; + }; +} + +/** + * @description A request from the client to the server, to ask for completion options. + * @category `completion/complete` + */ +export interface CompleteRequest extends Request { + method: 'completion/complete'; + params: CompleteRequestParams; +} + +/** + * @description The server's response to a completion/complete request + * @category `completion/complete` + */ +export interface CompleteResult extends Result { + completion: { + /** @description An array of completion values. Must not exceed 100 items. */ + values: string[]; + /** @description The total number of completion options available. This can exceed the number of values actually sent in the response. */ + total?: number; + /** @description Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. */ + hasMore?: boolean; + }; +} + +/** + * @description A reference to a resource or resource template definition. + * @category `completion/complete` + */ +export interface ResourceTemplateReference { + type: 'ref/resource'; + /** + * @description The URI or URI template of the resource. + * @format uri-template + */ + uri: string; +} + +/** + * @description Identifies a prompt. + * @category `completion/complete` + */ +export interface PromptReference extends BaseMetadata { + type: 'ref/prompt'; +} + +/* Roots */ +/** + * @description Sent from the server to request a list of root URIs from the client. Roots allow +servers to ask for specific directories or files to operate on. A common example +for roots is providing a set of repositories or directories a server should operate +on. + +This request is typically used when the server needs to understand the file system +structure or access specific locations that the client has permission to read from. + * @category `roots/list` + */ +export interface ListRootsRequest extends Request { + method: 'roots/list'; + params?: RequestParams; +} + +/** + * @description The client's response to a roots/list request from the server. +This result contains an array of Root objects, each representing a root directory +or file that the server can operate on. + * @category `roots/list` + */ +export interface ListRootsResult extends Result { + roots: Root[]; +} + +/** + * @description Represents a root directory or file that the server can operate on. + * @category `roots/list` + */ +export interface Root { + /** + * @description The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + * @format uri + */ + uri: string; + /** @description An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. */ + name?: string; + + /** @description See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. */ + _meta?: { [key: string]: unknown }; +} + +/** + * @description A notification from the client to the server, informing it that the list of roots has changed. +This notification should be sent whenever the client adds, removes, or modifies any root. +The server should then request an updated list of roots using the ListRootsRequest. + * @category `notifications/roots/list_changed` + */ +export interface RootsListChangedNotification extends Notification { + method: 'notifications/roots/list_changed'; + params?: NotificationParams; +} + +/** + * @description The parameters for a request to elicit non-sensitive information from the user via a form in the client. + * @category `elicitation/create` + */ +export interface ElicitRequestFormParams extends TaskAugmentedRequestParams { + /** @description The elicitation mode. */ + mode?: 'form'; + + /** @description The message to present to the user describing what information is being requested. */ + message: string; + + /** @description A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. */ + requestedSchema: { + $schema?: string; + type: 'object'; + properties: { + [key: string]: PrimitiveSchemaDefinition; + }; + required?: string[]; + }; +} + +/** + * @description The parameters for a request to elicit information from the user via a URL in the client. + * @category `elicitation/create` + */ +export interface ElicitRequestURLParams extends TaskAugmentedRequestParams { + /** @description The elicitation mode. */ + mode: 'url'; + + /** @description The message to present to the user explaining why the interaction is needed. */ + message: string; + + /** @description The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. */ + elicitationId: string; + + /** + * @description The URL that the user should navigate to. + * @format uri + */ + url: string; +} + +/** + * @description The parameters for a request to elicit additional information from the user via the client. + * @category `elicitation/create` + */ +export type ElicitRequestParams = ElicitRequestFormParams | ElicitRequestURLParams; + +/** + * @description A request from the server to elicit additional information from the user via the client. + * @category `elicitation/create` + */ +export interface ElicitRequest extends Request { + method: 'elicitation/create'; + params: ElicitRequestParams; +} + +/** + * @description Restricted schema definitions that only allow primitive types +without nested objects or arrays. + * @category `elicitation/create` + */ +export type PrimitiveSchemaDefinition = StringSchema | NumberSchema | BooleanSchema | EnumSchema; + +/** + * @category `elicitation/create` + */ +export interface StringSchema { + type: 'string'; + title?: string; + description?: string; + minLength?: number; + maxLength?: number; + format?: 'email' | 'uri' | 'date' | 'date-time'; + default?: string; +} + +/** + * @category `elicitation/create` + */ +export interface NumberSchema { + type: 'number' | 'integer'; + title?: string; + description?: string; + minimum?: number; + maximum?: number; + default?: number; +} + +/** + * @category `elicitation/create` + */ +export interface BooleanSchema { + type: 'boolean'; + title?: string; + description?: string; + default?: boolean; +} + +/** + * @description Schema for single-selection enumeration without display titles for options. + * @category `elicitation/create` + */ +export interface UntitledSingleSelectEnumSchema { + type: 'string'; + /** @description Optional title for the enum field. */ + title?: string; + /** @description Optional description for the enum field. */ + description?: string; + /** @description Array of enum values to choose from. */ + enum: string[]; + /** @description Optional default value. */ + default?: string; +} + +/** + * @description Schema for single-selection enumeration with display titles for each option. + * @category `elicitation/create` + */ +export interface TitledSingleSelectEnumSchema { + type: 'string'; + /** @description Optional title for the enum field. */ + title?: string; + /** @description Optional description for the enum field. */ + description?: string; + /** @description Array of enum options with values and display labels. */ + oneOf: Array<{ + /** + * The enum value. + */ + const: string; + /** + * Display label for this option. + */ + title: string; + }>; + /** @description Optional default value. */ + default?: string; +} + +/** + * @category `elicitation/create` + */ +// Combined single selection enumeration +export type SingleSelectEnumSchema = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema; + +/** + * @description Schema for multiple-selection enumeration without display titles for options. + * @category `elicitation/create` + */ +export interface UntitledMultiSelectEnumSchema { + type: 'array'; + /** @description Optional title for the enum field. */ + title?: string; + /** @description Optional description for the enum field. */ + description?: string; + /** @description Minimum number of items to select. */ + minItems?: number; + /** @description Maximum number of items to select. */ + maxItems?: number; + /** @description Schema for the array items. */ + items: { + type: 'string'; + /** @description Array of enum values to choose from. */ + enum: string[]; + }; + /** @description Optional default value. */ + default?: string[]; +} + +/** + * @description Schema for multiple-selection enumeration with display titles for each option. + * @category `elicitation/create` + */ +export interface TitledMultiSelectEnumSchema { + type: 'array'; + /** @description Optional title for the enum field. */ + title?: string; + /** @description Optional description for the enum field. */ + description?: string; + /** @description Minimum number of items to select. */ + minItems?: number; + /** @description Maximum number of items to select. */ + maxItems?: number; + /** @description Schema for array items with enum options and display labels. */ + items: { + /** @description Array of enum options with values and display labels. */ + anyOf: Array<{ + /** + * The constant enum value. + */ + const: string; + /** + * Display title for this option. + */ + title: string; + }>; + }; + /** @description Optional default value. */ + default?: string[]; +} + +/** + * @category `elicitation/create` + */ +// Combined multiple selection enumeration +export type MultiSelectEnumSchema = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema; + +/** + * @description Use TitledSingleSelectEnumSchema instead. +This interface will be removed in a future version. + * @category `elicitation/create` + */ +export interface LegacyTitledEnumSchema { + type: 'string'; + title?: string; + description?: string; + enum: string[]; + /** @description (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. */ + enumNames?: string[]; + default?: string; +} + +/** + * @category `elicitation/create` + */ +// Union type for all enum schemas +export type EnumSchema = SingleSelectEnumSchema | MultiSelectEnumSchema | LegacyTitledEnumSchema; + +/** + * @description The client's response to an elicitation request. + * @category `elicitation/create` + */ +export interface ElicitResult extends Result { + /** @description The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly decline the action + - "cancel": User dismissed without making an explicit choice */ + action: 'accept' | 'decline' | 'cancel'; + + /** @description The submitted form data, only present when action is "accept" and mode was "form". + Contains values matching the requested schema. + Omitted for out-of-band mode responses. */ + content?: { [key: string]: string | number | boolean | string[] }; +} + +/** + * @description An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. + * @category `notifications/elicitation/complete` + */ +export interface ElicitationCompleteNotification extends Notification { + method: 'notifications/elicitation/complete'; + params: { + /** @description The ID of the elicitation that completed. */ + elicitationId: string; + }; +} + +/* Client messages */ +/** @internal */ +export type ClientRequest = + | PingRequest + | InitializeRequest + | CompleteRequest + | SetLevelRequest + | GetPromptRequest + | ListPromptsRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | CallToolRequest + | ListToolsRequest + | GetTaskRequest + | GetTaskPayloadRequest + | ListTasksRequest + | CancelTaskRequest; + +/** @internal */ +export type ClientNotification = + | CancelledNotification + | ProgressNotification + | InitializedNotification + | RootsListChangedNotification + | TaskStatusNotification; + +/** @internal */ +export type ClientResult = + | EmptyResult + | CreateMessageResult + | ListRootsResult + | ElicitResult + | GetTaskResult + | GetTaskPayloadResult + | ListTasksResult + | CancelTaskResult; + +/* Server messages */ +/** @internal */ +export type ServerRequest = + | PingRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest + | GetTaskRequest + | GetTaskPayloadRequest + | ListTasksRequest + | CancelTaskRequest; + +/** @internal */ +export type ServerNotification = + | CancelledNotification + | ProgressNotification + | LoggingMessageNotification + | ResourceUpdatedNotification + | ResourceListChangedNotification + | ToolListChangedNotification + | PromptListChangedNotification + | ElicitationCompleteNotification + | TaskStatusNotification; + +/** @internal */ +export type ServerResult = + | EmptyResult + | InitializeResult + | CompleteResult + | GetPromptResult + | ListPromptsResult + | ListResourceTemplatesResult + | ListResourcesResult + | ReadResourceResult + | CallToolResult + | ListToolsResult + | GetTaskResult + | GetTaskPayloadResult + | ListTasksResult + | CancelTaskResult; +/** Extracted from ClientCapabilities["tasks"]. */ +export type ClientTasksCapability = { + /** @description Whether this client supports tasks/list. */ + list?: object; + /** @description Whether this client supports tasks/cancel. */ + cancel?: object; + /** @description Specifies which request types can be augmented with tasks. */ + requests?: { + /** @description Task support for sampling-related requests. */ + sampling?: { + /** @description Whether the client supports task-augmented sampling/createMessage requests. */ + createMessage?: object; + }; + /** @description Task support for elicitation-related requests. */ + elicitation?: { + /** @description Whether the client supports task-augmented elicitation/create requests. */ + create?: object; + }; + }; +}; +/** Extracted from ServerCapabilities["tasks"]. */ +export type ServerTasksCapability = { + /** @description Whether this server supports tasks/list. */ + list?: object; + /** @description Whether this server supports tasks/cancel. */ + cancel?: object; + /** @description Specifies which request types can be augmented with tasks. */ + requests?: { + /** @description Task support for tool-related requests. */ + tools?: { + /** @description Whether the server supports task-augmented tools/call requests. */ + call?: object; + }; + }; +}; diff --git a/src/server/index.ts b/src/server/index.ts index 531a559dd..17aa41b14 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -40,7 +40,10 @@ import { CreateTaskResultSchema, type Request, type Notification, - type Result + type Result, + type McpRequest, + type McpNotification, + type McpResult } from '../types.js'; import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js'; import type { JsonSchemaType, jsonSchemaValidator } from '../validation/types.js'; @@ -127,9 +130,9 @@ export type ServerOptions = ProtocolOptions & { * @deprecated Use `McpServer` instead for the high-level API. Only use `Server` for advanced use cases. */ export class Server< - RequestT extends Request = Request, - NotificationT extends Notification = Notification, - ResultT extends Result = Result + RequestT extends Request = McpRequest, + NotificationT extends Notification = McpNotification, + ResultT extends Result = McpResult > extends Protocol { private _clientCapabilities?: ClientCapabilities; private _clientVersion?: Implementation; diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index aa242a647..f31ae661b 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -20,7 +20,6 @@ import { JSONRPCErrorResponse, JSONRPCNotification, JSONRPCRequest, - JSONRPCResponse, McpError, PingRequestSchema, Progress, @@ -28,7 +27,9 @@ import { ProgressNotificationSchema, RELATED_TASK_META_KEY, RequestId, + Request, Result, + Notification, ServerCapabilities, RequestMeta, MessageExtraInfo, @@ -40,8 +41,6 @@ import { Task, TaskStatusNotification, TaskStatusNotificationSchema, - Request, - Notification, JSONRPCResultResponse, isTaskAugmentedRequestParams } from '../types.js'; @@ -361,7 +360,7 @@ export abstract class Protocol Promise; + fallbackNotificationHandler?: (notification: JSONRPCNotification) => Promise; constructor(private _options?: ProtocolOptions) { this.setNotificationHandler(CancelledNotificationSchema, notification => { @@ -390,10 +389,9 @@ export abstract class Protocol { @@ -474,7 +472,7 @@ export abstract class Protocol { try { const { tasks, nextCursor } = await this._taskStore!.listTasks(request.params?.cursor, extra.sessionId); - // @ts-expect-error SendResultT cannot contain ListTasksResult, but we include it in our derived types everywhere else return { tasks, nextCursor, _meta: {} - } as SendResultT; + } as unknown as SendResultT; } catch (error) { throw new McpError( ErrorCode.InvalidParams, @@ -769,8 +766,8 @@ export abstract class Protocol })?._meta || {}; jsonrpcRequest.params = { ...request.params, _meta: { - ...(request.params?._meta || {}), + ...existingMeta, progressToken: messageId } }; @@ -1286,13 +1284,14 @@ export abstract class Protocol })?._meta || {}; const jsonrpcNotification: JSONRPCNotification = { ...notification, jsonrpc: '2.0', params: { ...notification.params, _meta: { - ...(notification.params?._meta || {}), + ...existingMeta, [RELATED_TASK_META_KEY]: options.relatedTask } } @@ -1559,10 +1558,8 @@ export abstract class Protocol = T extends object ? (T extends infer O ? { [K in keyof O]: ExpandRecursively } : never) : T; -/** - * Assert 'object' type schema. - * - * @internal - */ -const AssertObjectSchema = z.custom((v): v is object => v !== null && (typeof v === 'object' || typeof v === 'function')); -/** - * A progress token, used to associate progress notifications with the original request. - */ -export const ProgressTokenSchema = z.union([z.string(), z.number().int()]); - -/** - * An opaque token used to represent a cursor for pagination. - */ -export const CursorSchema = z.string(); - -/** - * Task creation parameters, used to ask that the server create a task to represent a request. - */ -export const TaskCreationParamsSchema = z.looseObject({ - /** - * Time in milliseconds to keep task results available after completion. - * If null, the task has unlimited lifetime until manually cleaned up. - */ - ttl: z.union([z.number(), z.null()]).optional(), - - /** - * Time in milliseconds to wait between task status requests. - */ - pollInterval: z.number().optional() -}); - -export const TaskMetadataSchema = z.object({ - ttl: z.number().optional() -}); - -/** - * Metadata for associating messages with a task. - * Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. - */ -export const RelatedTaskMetadataSchema = z.object({ - taskId: z.string() -}); - -const RequestMetaSchema = z.looseObject({ - /** - * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. - */ - progressToken: ProgressTokenSchema.optional(), - /** - * If specified, this request is related to the provided task. - */ - [RELATED_TASK_META_KEY]: RelatedTaskMetadataSchema.optional() -}); - -/** - * Common params for any request. - */ -const BaseRequestParamsSchema = z.object({ - /** - * See [General fields: `_meta`](/specification/draft/basic/index#meta) for notes on `_meta` usage. - */ - _meta: RequestMetaSchema.optional() -}); - -/** - * Common params for any task-augmented request. - */ -export const TaskAugmentedRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * If specified, the caller is requesting task-augmented execution for this request. - * The request will return a CreateTaskResult immediately, and the actual result can be - * retrieved later via tasks/result. - * - * Task augmentation is subject to capability negotiation - receivers MUST declare support - * for task augmentation of specific request types in their capabilities. - */ - task: TaskMetadataSchema.optional() -}); - -/** - * Checks if a value is a valid TaskAugmentedRequestParams. - * @param value - The value to check. - * - * @returns True if the value is a valid TaskAugmentedRequestParams, false otherwise. - */ -export const isTaskAugmentedRequestParams = (value: unknown): value is TaskAugmentedRequestParams => - TaskAugmentedRequestParamsSchema.safeParse(value).success; - -export const RequestSchema = z.object({ - method: z.string(), - params: BaseRequestParamsSchema.loose().optional() -}); - -const NotificationsParamsSchema = z.object({ - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: RequestMetaSchema.optional() -}); - -export const NotificationSchema = z.object({ - method: z.string(), - params: NotificationsParamsSchema.loose().optional() -}); - -export const ResultSchema = z.looseObject({ - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: RequestMetaSchema.optional() -}); - -/** - * A uniquely identifying ID for a request in JSON-RPC. - */ -export const RequestIdSchema = z.union([z.string(), z.number().int()]); - -/** - * A request that expects a response. - */ -export const JSONRPCRequestSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - id: RequestIdSchema, - ...RequestSchema.shape - }) - .strict(); - -export const isJSONRPCRequest = (value: unknown): value is JSONRPCRequest => JSONRPCRequestSchema.safeParse(value).success; - -/** - * A notification which does not expect a response. - */ -export const JSONRPCNotificationSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - ...NotificationSchema.shape - }) - .strict(); - -export const isJSONRPCNotification = (value: unknown): value is JSONRPCNotification => JSONRPCNotificationSchema.safeParse(value).success; - -/** - * A successful (non-error) response to a request. - */ -export const JSONRPCResultResponseSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - id: RequestIdSchema, - result: ResultSchema - }) - .strict(); - -export const isJSONRPCResultResponse = (value: unknown): value is JSONRPCResultResponse => - JSONRPCResultResponseSchema.safeParse(value).success; - -/** - * Error codes defined by the JSON-RPC specification. - */ -export enum ErrorCode { - // SDK error codes - ConnectionClosed = -32000, - RequestTimeout = -32001, - - // Standard JSON-RPC error codes - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - - // MCP-specific error codes - UrlElicitationRequired = -32042 -} - -/** - * A response to a request that indicates an error occurred. - */ -export const JSONRPCErrorResponseSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - id: RequestIdSchema.optional(), - error: z.object({ - /** - * The error type that occurred. - */ - code: z.number().int(), - /** - * A short description of the error. The message SHOULD be limited to a concise single sentence. - */ - message: z.string(), - /** - * Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). - */ - data: z.unknown().optional() - }) - }) - .strict(); - -export const isJSONRPCErrorResponse = (value: unknown): value is JSONRPCErrorResponse => - JSONRPCErrorResponseSchema.safeParse(value).success; - -export const JSONRPCMessageSchema = z.union([ - JSONRPCRequestSchema, - JSONRPCNotificationSchema, - JSONRPCResultResponseSchema, - JSONRPCErrorResponseSchema -]); -export const JSONRPCResponseSchema = z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]); - -/* Empty result */ -/** - * A response that indicates success but carries no data. - */ -export const EmptyResultSchema = ResultSchema.strict(); - -export const CancelledNotificationParamsSchema = NotificationsParamsSchema.extend({ - /** - * The ID of the request to cancel. - * - * This MUST correspond to the ID of a request previously issued in the same direction. - */ - requestId: RequestIdSchema.optional(), - /** - * An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. - */ - reason: z.string().optional() -}); -/* Cancellation */ -/** - * This notification can be sent by either side to indicate that it is cancelling a previously-issued request. - * - * The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. - * - * This notification indicates that the result will be unused, so any associated processing SHOULD cease. - * - * A client MUST NOT attempt to cancel its `initialize` request. - */ -export const CancelledNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/cancelled'), - params: CancelledNotificationParamsSchema -}); - -/* Base Metadata */ -/** - * Icon schema for use in tools, prompts, resources, and implementations. - */ -export const IconSchema = z.object({ - /** - * URL or data URI for the icon. - */ - src: z.string(), - /** - * Optional MIME type for the icon. - */ - mimeType: z.string().optional(), - /** - * Optional array of strings that specify sizes at which the icon can be used. - * Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. - * - * If not provided, the client should assume that the icon can be used at any size. - */ - sizes: z.array(z.string()).optional() -}); - -/** - * Base schema to add `icons` property. - * - */ -export const IconsSchema = z.object({ - /** - * Optional set of sized icons that the client can display in a user interface. - * - * Clients that support rendering icons MUST support at least the following MIME types: - * - `image/png` - PNG images (safe, universal compatibility) - * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) - * - * Clients that support rendering icons SHOULD also support: - * - `image/svg+xml` - SVG images (scalable but requires security precautions) - * - `image/webp` - WebP images (modern, efficient format) - */ - icons: z.array(IconSchema).optional() -}); - -/** - * Base metadata interface for common properties across resources, tools, prompts, and implementations. - */ -export const BaseMetadataSchema = z.object({ - /** Intended for programmatic or logical use, but used as a display name in past specs or fallback */ - name: z.string(), - /** - * Intended for UI and end-user contexts — optimized to be human-readable and easily understood, - * even by those unfamiliar with domain-specific terminology. - * - * If not provided, the name should be used for display (except for Tool, - * where `annotations.title` should be given precedence over using `name`, - * if present). - */ - title: z.string().optional() -}); - -/* Initialization */ -/** - * Describes the name and version of an MCP implementation. - */ -export const ImplementationSchema = BaseMetadataSchema.extend({ - ...BaseMetadataSchema.shape, - ...IconsSchema.shape, - version: z.string(), - /** - * An optional URL of the website for this implementation. - */ - websiteUrl: z.string().optional() -}); - -const FormElicitationCapabilitySchema = z.intersection( - z.object({ - applyDefaults: z.boolean().optional() - }), - z.record(z.string(), z.unknown()) -); - -const ElicitationCapabilitySchema = z.preprocess( - value => { - if (value && typeof value === 'object' && !Array.isArray(value)) { - if (Object.keys(value as Record).length === 0) { - return { form: {} }; - } - } - return value; - }, - z.intersection( - z.object({ - form: FormElicitationCapabilitySchema.optional(), - url: AssertObjectSchema.optional() - }), - z.record(z.string(), z.unknown()).optional() - ) -); - -/** - * Task capabilities for clients, indicating which request types support task creation. - */ -export const ClientTasksCapabilitySchema = z.looseObject({ - /** - * Present if the client supports listing tasks. - */ - list: AssertObjectSchema.optional(), - /** - * Present if the client supports cancelling tasks. - */ - cancel: AssertObjectSchema.optional(), - /** - * Capabilities for task creation on specific request types. - */ - requests: z - .looseObject({ - /** - * Task support for sampling requests. - */ - sampling: z - .looseObject({ - createMessage: AssertObjectSchema.optional() - }) - .optional(), - /** - * Task support for elicitation requests. - */ - elicitation: z - .looseObject({ - create: AssertObjectSchema.optional() - }) - .optional() - }) - .optional() -}); - -/** - * Task capabilities for servers, indicating which request types support task creation. - */ -export const ServerTasksCapabilitySchema = z.looseObject({ - /** - * Present if the server supports listing tasks. - */ - list: AssertObjectSchema.optional(), - /** - * Present if the server supports cancelling tasks. - */ - cancel: AssertObjectSchema.optional(), - /** - * Capabilities for task creation on specific request types. - */ - requests: z - .looseObject({ - /** - * Task support for tool requests. - */ - tools: z - .looseObject({ - call: AssertObjectSchema.optional() - }) - .optional() - }) - .optional() -}); - -/** - * Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. - */ -export const ClientCapabilitiesSchema = z.object({ - /** - * Experimental, non-standard capabilities that the client supports. - */ - experimental: z.record(z.string(), AssertObjectSchema).optional(), - /** - * Present if the client supports sampling from an LLM. - */ - sampling: z - .object({ - /** - * Present if the client supports context inclusion via includeContext parameter. - * If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). - */ - context: AssertObjectSchema.optional(), - /** - * Present if the client supports tool use via tools and toolChoice parameters. - */ - tools: AssertObjectSchema.optional() - }) - .optional(), - /** - * Present if the client supports eliciting user input. - */ - elicitation: ElicitationCapabilitySchema.optional(), - /** - * Present if the client supports listing roots. - */ - roots: z - .object({ - /** - * Whether the client supports issuing notifications for changes to the roots list. - */ - listChanged: z.boolean().optional() - }) - .optional(), - /** - * Present if the client supports task creation. - */ - tasks: ClientTasksCapabilitySchema.optional() -}); - -export const InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. - */ - protocolVersion: z.string(), - capabilities: ClientCapabilitiesSchema, - clientInfo: ImplementationSchema -}); -/** - * This request is sent from the client to the server when it first connects, asking it to begin initialization. - */ -export const InitializeRequestSchema = RequestSchema.extend({ - method: z.literal('initialize'), - params: InitializeRequestParamsSchema -}); - -export const isInitializeRequest = (value: unknown): value is InitializeRequest => InitializeRequestSchema.safeParse(value).success; - -/** - * Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. - */ -export const ServerCapabilitiesSchema = z.object({ - /** - * Experimental, non-standard capabilities that the server supports. - */ - experimental: z.record(z.string(), AssertObjectSchema).optional(), - /** - * Present if the server supports sending log messages to the client. - */ - logging: AssertObjectSchema.optional(), - /** - * Present if the server supports sending completions to the client. - */ - completions: AssertObjectSchema.optional(), - /** - * Present if the server offers any prompt templates. - */ - prompts: z - .object({ - /** - * Whether this server supports issuing notifications for changes to the prompt list. - */ - listChanged: z.boolean().optional() - }) - .optional(), - /** - * Present if the server offers any resources to read. - */ - resources: z - .object({ - /** - * Whether this server supports clients subscribing to resource updates. - */ - subscribe: z.boolean().optional(), - - /** - * Whether this server supports issuing notifications for changes to the resource list. - */ - listChanged: z.boolean().optional() - }) - .optional(), - /** - * Present if the server offers any tools to call. - */ - tools: z - .object({ - /** - * Whether this server supports issuing notifications for changes to the tool list. - */ - listChanged: z.boolean().optional() - }) - .optional(), - /** - * Present if the server supports task creation. - */ - tasks: ServerTasksCapabilitySchema.optional() -}); - -/** - * After receiving an initialize request from the client, the server sends this response. - */ -export const InitializeResultSchema = ResultSchema.extend({ - /** - * The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. - */ - protocolVersion: z.string(), - capabilities: ServerCapabilitiesSchema, - serverInfo: ImplementationSchema, - /** - * Instructions describing how to use the server and its features. - * - * This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. - */ - instructions: z.string().optional() -}); - -/** - * This notification is sent from the client to the server after initialization has finished. - */ -export const InitializedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/initialized'), - params: NotificationsParamsSchema.optional() -}); - -export const isInitializedNotification = (value: unknown): value is InitializedNotification => - InitializedNotificationSchema.safeParse(value).success; - -/* Ping */ -/** - * A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. - */ -export const PingRequestSchema = RequestSchema.extend({ - method: z.literal('ping'), - params: BaseRequestParamsSchema.optional() -}); - -/* Progress notifications */ -export const ProgressSchema = z.object({ - /** - * The progress thus far. This should increase every time progress is made, even if the total is unknown. - */ - progress: z.number(), - /** - * Total number of items to process (or total progress required), if known. - */ - total: z.optional(z.number()), - /** - * An optional message describing the current progress. - */ - message: z.optional(z.string()) -}); - -export const ProgressNotificationParamsSchema = z.object({ - ...NotificationsParamsSchema.shape, - ...ProgressSchema.shape, - /** - * The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. - */ - progressToken: ProgressTokenSchema -}); -/** - * An out-of-band notification used to inform the receiver of a progress update for a long-running request. - * - * @category notifications/progress - */ -export const ProgressNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/progress'), - params: ProgressNotificationParamsSchema -}); - -export const PaginatedRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * An opaque token representing the current pagination position. - * If provided, the server should return results starting after this cursor. - */ - cursor: CursorSchema.optional() -}); - -/* Pagination */ -export const PaginatedRequestSchema = RequestSchema.extend({ - params: PaginatedRequestParamsSchema.optional() -}); - -export const PaginatedResultSchema = ResultSchema.extend({ - /** - * An opaque token representing the pagination position after the last returned result. - * If present, there may be more results available. - */ - nextCursor: CursorSchema.optional() -}); - -/** - * The status of a task. - * */ -export const TaskStatusSchema = z.enum(['working', 'input_required', 'completed', 'failed', 'cancelled']); - -/* Tasks */ -/** - * A pollable state object associated with a request. - */ -export const TaskSchema = z.object({ - taskId: z.string(), - status: TaskStatusSchema, - /** - * Time in milliseconds to keep task results available after completion. - * If null, the task has unlimited lifetime until manually cleaned up. - */ - ttl: z.union([z.number(), z.null()]), - /** - * ISO 8601 timestamp when the task was created. - */ - createdAt: z.string(), - /** - * ISO 8601 timestamp when the task was last updated. - */ - lastUpdatedAt: z.string(), - pollInterval: z.optional(z.number()), - /** - * Optional diagnostic message for failed tasks or other status information. - */ - statusMessage: z.optional(z.string()) -}); - -/** - * Result returned when a task is created, containing the task data wrapped in a task field. - */ -export const CreateTaskResultSchema = ResultSchema.extend({ - task: TaskSchema -}); - -/** - * Parameters for task status notification. - */ -export const TaskStatusNotificationParamsSchema = NotificationsParamsSchema.merge(TaskSchema); - -/** - * A notification sent when a task's status changes. - */ -export const TaskStatusNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/tasks/status'), - params: TaskStatusNotificationParamsSchema -}); - -/** - * A request to get the state of a specific task. - */ -export const GetTaskRequestSchema = RequestSchema.extend({ - method: z.literal('tasks/get'), - params: BaseRequestParamsSchema.extend({ - taskId: z.string() - }) -}); - -/** - * The response to a tasks/get request. - */ -export const GetTaskResultSchema = ResultSchema.merge(TaskSchema); - -/** - * A request to get the result of a specific task. - */ -export const GetTaskPayloadRequestSchema = RequestSchema.extend({ - method: z.literal('tasks/result'), - params: BaseRequestParamsSchema.extend({ - taskId: z.string() - }) -}); - -/** - * The response to a tasks/result request. - * The structure matches the result type of the original request. - * For example, a tools/call task would return the CallToolResult structure. - * - */ -export const GetTaskPayloadResultSchema = ResultSchema.loose(); - -/** - * A request to list tasks. - */ -export const ListTasksRequestSchema = PaginatedRequestSchema.extend({ - method: z.literal('tasks/list') -}); - -/** - * The response to a tasks/list request. - */ -export const ListTasksResultSchema = PaginatedResultSchema.extend({ - tasks: z.array(TaskSchema) -}); - -/** - * A request to cancel a specific task. - */ -export const CancelTaskRequestSchema = RequestSchema.extend({ - method: z.literal('tasks/cancel'), - params: BaseRequestParamsSchema.extend({ - taskId: z.string() - }) -}); - -/** - * The response to a tasks/cancel request. - */ -export const CancelTaskResultSchema = ResultSchema.merge(TaskSchema); - -/* Resources */ -/** - * The contents of a specific resource or sub-resource. - */ -export const ResourceContentsSchema = z.object({ - /** - * The URI of this resource. - */ - uri: z.string(), - /** - * The MIME type of this resource, if known. - */ - mimeType: z.optional(z.string()), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -export const TextResourceContentsSchema = ResourceContentsSchema.extend({ - /** - * The text of the item. This must only be set if the item can actually be represented as text (not binary data). - */ - text: z.string() -}); - -/** - * A Zod schema for validating Base64 strings that is more performant and - * robust for very large inputs than the default regex-based check. It avoids - * stack overflows by using the native `atob` function for validation. - */ -const Base64Schema = z.string().refine( - val => { - try { - // atob throws a DOMException if the string contains characters - // that are not part of the Base64 character set. - atob(val); - return true; - } catch { - return false; - } - }, - { message: 'Invalid Base64 string' } -); - -export const BlobResourceContentsSchema = ResourceContentsSchema.extend({ - /** - * A base64-encoded string representing the binary data of the item. - */ - blob: Base64Schema -}); - -/** - * The sender or recipient of messages and data in a conversation. - */ -export const RoleSchema = z.enum(['user', 'assistant']); - -/** - * Optional annotations providing clients additional context about a resource. - */ -export const AnnotationsSchema = z.object({ - /** - * Intended audience(s) for the resource. - */ - audience: z.array(RoleSchema).optional(), - - /** - * Importance hint for the resource, from 0 (least) to 1 (most). - */ - priority: z.number().min(0).max(1).optional(), - - /** - * ISO 8601 timestamp for the most recent modification. - */ - lastModified: z.iso.datetime({ offset: true }).optional() -}); - -/** - * A known resource that the server is capable of reading. - */ -export const ResourceSchema = z.object({ - ...BaseMetadataSchema.shape, - ...IconsSchema.shape, - /** - * The URI of this resource. - */ - uri: z.string(), - - /** - * A description of what this resource represents. - * - * This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. - */ - description: z.optional(z.string()), - - /** - * The MIME type of this resource, if known. - */ - mimeType: z.optional(z.string()), - - /** - * Optional annotations for the client. - */ - annotations: AnnotationsSchema.optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.looseObject({})) -}); - -/** - * A template description for resources available on the server. - */ -export const ResourceTemplateSchema = z.object({ - ...BaseMetadataSchema.shape, - ...IconsSchema.shape, - /** - * A URI template (according to RFC 6570) that can be used to construct resource URIs. - */ - uriTemplate: z.string(), - - /** - * A description of what this template is for. - * - * This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. - */ - description: z.optional(z.string()), - - /** - * The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. - */ - mimeType: z.optional(z.string()), - - /** - * Optional annotations for the client. - */ - annotations: AnnotationsSchema.optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.looseObject({})) -}); - -/** - * Sent from the client to request a list of resources the server has. - */ -export const ListResourcesRequestSchema = PaginatedRequestSchema.extend({ - method: z.literal('resources/list') -}); - -/** - * The server's response to a resources/list request from the client. - */ -export const ListResourcesResultSchema = PaginatedResultSchema.extend({ - resources: z.array(ResourceSchema) -}); - -/** - * Sent from the client to request a list of resource templates the server has. - */ -export const ListResourceTemplatesRequestSchema = PaginatedRequestSchema.extend({ - method: z.literal('resources/templates/list') -}); - -/** - * The server's response to a resources/templates/list request from the client. - */ -export const ListResourceTemplatesResultSchema = PaginatedResultSchema.extend({ - resourceTemplates: z.array(ResourceTemplateSchema) -}); - -export const ResourceRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. - * - * @format uri - */ - uri: z.string() -}); - -/** - * Parameters for a `resources/read` request. - */ -export const ReadResourceRequestParamsSchema = ResourceRequestParamsSchema; - -/** - * Sent from the client to the server, to read a specific resource URI. - */ -export const ReadResourceRequestSchema = RequestSchema.extend({ - method: z.literal('resources/read'), - params: ReadResourceRequestParamsSchema -}); - -/** - * The server's response to a resources/read request from the client. - */ -export const ReadResourceResultSchema = ResultSchema.extend({ - contents: z.array(z.union([TextResourceContentsSchema, BlobResourceContentsSchema])) -}); - -/** - * An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. - */ -export const ResourceListChangedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/resources/list_changed'), - params: NotificationsParamsSchema.optional() -}); - -export const SubscribeRequestParamsSchema = ResourceRequestParamsSchema; -/** - * Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. - */ -export const SubscribeRequestSchema = RequestSchema.extend({ - method: z.literal('resources/subscribe'), - params: SubscribeRequestParamsSchema -}); - -export const UnsubscribeRequestParamsSchema = ResourceRequestParamsSchema; -/** - * Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. - */ -export const UnsubscribeRequestSchema = RequestSchema.extend({ - method: z.literal('resources/unsubscribe'), - params: UnsubscribeRequestParamsSchema -}); - -/** - * Parameters for a `notifications/resources/updated` notification. - */ -export const ResourceUpdatedNotificationParamsSchema = NotificationsParamsSchema.extend({ - /** - * The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. - */ - uri: z.string() -}); - -/** - * A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. - */ -export const ResourceUpdatedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/resources/updated'), - params: ResourceUpdatedNotificationParamsSchema -}); - -/* Prompts */ -/** - * Describes an argument that a prompt can accept. - */ -export const PromptArgumentSchema = z.object({ - /** - * The name of the argument. - */ - name: z.string(), - /** - * A human-readable description of the argument. - */ - description: z.optional(z.string()), - /** - * Whether this argument must be provided. - */ - required: z.optional(z.boolean()) -}); - -/** - * A prompt or prompt template that the server offers. - */ -export const PromptSchema = z.object({ - ...BaseMetadataSchema.shape, - ...IconsSchema.shape, - /** - * An optional description of what this prompt provides - */ - description: z.optional(z.string()), - /** - * A list of arguments to use for templating the prompt. - */ - arguments: z.optional(z.array(PromptArgumentSchema)), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.looseObject({})) -}); - -/** - * Sent from the client to request a list of prompts and prompt templates the server has. - */ -export const ListPromptsRequestSchema = PaginatedRequestSchema.extend({ - method: z.literal('prompts/list') -}); - -/** - * The server's response to a prompts/list request from the client. - */ -export const ListPromptsResultSchema = PaginatedResultSchema.extend({ - prompts: z.array(PromptSchema) -}); - -/** - * Parameters for a `prompts/get` request. - */ -export const GetPromptRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * The name of the prompt or prompt template. - */ - name: z.string(), - /** - * Arguments to use for templating the prompt. - */ - arguments: z.record(z.string(), z.string()).optional() -}); -/** - * Used by the client to get a prompt provided by the server. - */ -export const GetPromptRequestSchema = RequestSchema.extend({ - method: z.literal('prompts/get'), - params: GetPromptRequestParamsSchema -}); - -/** - * Text provided to or from an LLM. - */ -export const TextContentSchema = z.object({ - type: z.literal('text'), - /** - * The text content of the message. - */ - text: z.string(), - - /** - * Optional annotations for the client. - */ - annotations: AnnotationsSchema.optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * An image provided to or from an LLM. - */ -export const ImageContentSchema = z.object({ - type: z.literal('image'), - /** - * The base64-encoded image data. - */ - data: Base64Schema, - /** - * The MIME type of the image. Different providers may support different image types. - */ - mimeType: z.string(), - - /** - * Optional annotations for the client. - */ - annotations: AnnotationsSchema.optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * An Audio provided to or from an LLM. - */ -export const AudioContentSchema = z.object({ - type: z.literal('audio'), - /** - * The base64-encoded audio data. - */ - data: Base64Schema, - /** - * The MIME type of the audio. Different providers may support different audio types. - */ - mimeType: z.string(), - - /** - * Optional annotations for the client. - */ - annotations: AnnotationsSchema.optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * A tool call request from an assistant (LLM). - * Represents the assistant's request to use a tool. - */ -export const ToolUseContentSchema = z.object({ - type: z.literal('tool_use'), - /** - * The name of the tool to invoke. - * Must match a tool name from the request's tools array. - */ - name: z.string(), - /** - * Unique identifier for this tool call. - * Used to correlate with ToolResultContent in subsequent messages. - */ - id: z.string(), - /** - * Arguments to pass to the tool. - * Must conform to the tool's inputSchema. - */ - input: z.record(z.string(), z.unknown()), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); +type ExpandRecursively = T extends object ? (T extends infer O ? { [K in keyof O]: ExpandRecursively } : never) : T; /** - * The contents of a resource, embedded into a prompt or tool call result. + * Task creation parameters, used to ask that the server create a task to represent a request. */ -export const EmbeddedResourceSchema = z.object({ - type: z.literal('resource'), - resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), +export const TaskCreationParamsSchema = z.looseObject({ /** - * Optional annotations for the client. + * Time in milliseconds to keep task results available after completion. + * If null, the task has unlimited lifetime until manually cleaned up. */ - annotations: AnnotationsSchema.optional(), + ttl: z.union([z.number(), z.null()]).optional(), + /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. + * Time in milliseconds to wait between task status requests. */ - _meta: z.record(z.string(), z.unknown()).optional() + pollInterval: z.number().optional() }); +// during pre-processing. + /** - * A resource that the server is capable of reading, included in a prompt or tool call result. + * Checks if a value is a valid TaskAugmentedRequestParams. + * @param value - The value to check. * - * Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + * @returns True if the value is a valid TaskAugmentedRequestParams, false otherwise. */ -export const ResourceLinkSchema = ResourceSchema.extend({ - type: z.literal('resource_link') -}); +export const isTaskAugmentedRequestParams = (value: unknown): value is TaskAugmentedRequestParams => + TaskAugmentedRequestParamsSchema.safeParse(value).success; -/** - * A content block that can be used in prompts and tool results. - */ -export const ContentBlockSchema = z.union([ - TextContentSchema, - ImageContentSchema, - AudioContentSchema, - ResourceLinkSchema, - EmbeddedResourceSchema -]); +export const isJSONRPCRequest = (value: unknown): value is JSONRPCRequest => JSONRPCRequestSchema.safeParse(value).success; +export const isJSONRPCNotification = (value: unknown): value is JSONRPCNotification => JSONRPCNotificationSchema.safeParse(value).success; +export const isJSONRPCResultResponse = (value: unknown): value is JSONRPCResultResponse => + JSONRPCResultResponseSchema.safeParse(value).success; +export const isJSONRPCErrorResponse = (value: unknown): value is JSONRPCErrorResponse => + JSONRPCErrorResponseSchema.safeParse(value).success; /** - * Describes a message returned as part of a prompt. + * Error codes defined by the JSON-RPC specification. */ -export const PromptMessageSchema = z.object({ - role: RoleSchema, - content: ContentBlockSchema -}); +export enum ErrorCode { + // SDK error codes + ConnectionClosed = -32000, + RequestTimeout = -32001, -/** - * The server's response to a prompts/get request from the client. - */ -export const GetPromptResultSchema = ResultSchema.extend({ - /** - * An optional description for the prompt. - */ - description: z.string().optional(), - messages: z.array(PromptMessageSchema) -}); + // Standard JSON-RPC error codes + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, -/** - * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. - */ -export const PromptListChangedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/prompts/list_changed'), - params: NotificationsParamsSchema.optional() -}); + // MCP-specific error codes + UrlElicitationRequired = -32042 +} -/* Tools */ +export const JSONRPCResponseSchema = z.union([JSONRPCResultResponseSchema, JSONRPCErrorResponseSchema]); + +/* Cancellation */ /** - * Additional properties describing a Tool to clients. - * - * NOTE: all properties in ToolAnnotations are **hints**. - * They are not guaranteed to provide a faithful description of - * tool behavior (including descriptive properties like `title`). + * This notification can be sent by either side to indicate that it is cancelling a previously-issued request. * - * Clients should never make tool use decisions based on ToolAnnotations - * received from untrusted servers. + * Note: CancelledNotificationSchema is re-exported from generated. */ -export const ToolAnnotationsSchema = z.object({ - /** - * A human-readable title for the tool. - */ - title: z.string().optional(), - /** - * If true, the tool does not modify its environment. - * - * Default: false - */ - readOnlyHint: z.boolean().optional(), +/* Initialization */ - /** - * If true, the tool may perform destructive updates to its environment. - * If false, the tool performs only additive updates. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: true - */ - destructiveHint: z.boolean().optional(), +/** + * Elicitation capability schema - extracted from ClientCapabilitiesSchema. + * Has special preprocessing to handle empty objects as { form: {} } for backwards compatibility. + */ +export const ElicitationCapabilitySchema = ClientCapabilitiesSchema.shape.elicitation.unwrap(); - /** - * If true, calling the tool repeatedly with the same arguments - * will have no additional effect on the its environment. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: false - */ - idempotentHint: z.boolean().optional(), +export const isInitializeRequest = (value: unknown): value is InitializeRequest => InitializeRequestSchema.safeParse(value).success; - /** - * If true, this tool may interact with an "open world" of external - * entities. If false, the tool's domain of interaction is closed. - * For example, the world of a web search tool is open, whereas that - * of a memory tool is not. - * - * Default: true - */ - openWorldHint: z.boolean().optional() -}); +export const isInitializedNotification = (value: unknown): value is InitializedNotification => + InitializedNotificationSchema.safeParse(value).success; -/** - * Execution-related properties for a tool. - */ -export const ToolExecutionSchema = z.object({ - /** - * Indicates the tool's preference for task-augmented execution. - * - "required": Clients MUST invoke the tool as a task - * - "optional": Clients MAY invoke the tool as a task or normal request - * - "forbidden": Clients MUST NOT attempt to invoke the tool as a task - * - * If not present, defaults to "forbidden". - */ - taskSupport: z.enum(['required', 'optional', 'forbidden']).optional() -}); +/* Ping */ +/* Progress notifications */ /** - * Definition for a tool the client can call. + * Progress schema - derived from ProgressNotificationParams without progressToken. + * Used for the ProgressCallback signature in RequestOptions. */ -export const ToolSchema = z.object({ - ...BaseMetadataSchema.shape, - ...IconsSchema.shape, - /** - * A human-readable description of the tool. - */ - description: z.string().optional(), - /** - * A JSON Schema 2020-12 object defining the expected parameters for the tool. - * Must have type: 'object' at the root level per MCP spec. - */ - inputSchema: z - .object({ - type: z.literal('object'), - properties: z.record(z.string(), AssertObjectSchema).optional(), - required: z.array(z.string()).optional() - }) - .catchall(z.unknown()), - /** - * An optional JSON Schema 2020-12 object defining the structure of the tool's output - * returned in the structuredContent field of a CallToolResult. - * Must have type: 'object' at the root level per MCP spec. - */ - outputSchema: z - .object({ - type: z.literal('object'), - properties: z.record(z.string(), AssertObjectSchema).optional(), - required: z.array(z.string()).optional() - }) - .catchall(z.unknown()) - .optional(), - /** - * Optional additional tool information. - */ - annotations: ToolAnnotationsSchema.optional(), - /** - * Execution-related properties for this tool. - */ - execution: ToolExecutionSchema.optional(), +export const ProgressSchema = ProgressNotificationParamsSchema.omit({ progressToken: true }); - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); +/* Pagination */ -/** - * Sent from the client to request a list of tools the server has. - */ -export const ListToolsRequestSchema = PaginatedRequestSchema.extend({ - method: z.literal('tools/list') -}); +/* Tasks */ -/** - * The server's response to a tools/list request from the client. - */ -export const ListToolsResultSchema = PaginatedResultSchema.extend({ - tools: z.array(ToolSchema) -}); +/* Resources */ -/** - * The server's response to a tool call. - */ -export const CallToolResultSchema = ResultSchema.extend({ - /** - * A list of content objects that represent the result of the tool call. - * - * If the Tool does not define an outputSchema, this field MUST be present in the result. - * For backwards compatibility, this field is always present, but it may be empty. - */ - content: z.array(ContentBlockSchema).default([]), +// ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, +// SubscribeRequestParamsSchema, UnsubscribeRequestParamsSchema, +// ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, - /** - * An object containing structured tool output. - * - * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema. - */ - structuredContent: z.record(z.string(), z.unknown()).optional(), +/* Prompts */ - /** - * Whether the tool call ended in an error. - * - * If not set, this is assumed to be false (the call was successful). - * - * Any errors that originate from the tool SHOULD be reported inside the result - * object, with `isError` set to true, _not_ as an MCP protocol-level error - * response. Otherwise, the LLM would not be able to see that an error occurred - * and self-correct. - * - * However, any errors in _finding_ the tool, an error indicating that the - * server does not support tool calls, or any other exceptional conditions, - * should be reported as an MCP error response. - */ - isError: z.boolean().optional() -}); +// from generated with Base64 validation for data fields. + +/* Tools */ +// ListToolsResultSchema, CallToolResultSchema, CallToolRequestParamsSchema, /** * CallToolResultSchema extended with backwards compatibility to protocol version 2024-10-07. @@ -1422,36 +501,6 @@ export const CompatibilityCallToolResultSchema = CallToolResultSchema.or( }) ); -/** - * Parameters for a `tools/call` request. - */ -export const CallToolRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({ - /** - * The name of the tool to call. - */ - name: z.string(), - /** - * Arguments to pass to the tool. - */ - arguments: z.record(z.string(), z.unknown()).optional() -}); - -/** - * Used by the client to invoke a tool provided by the server. - */ -export const CallToolRequestSchema = RequestSchema.extend({ - method: z.literal('tools/call'), - params: CallToolRequestParamsSchema -}); - -/** - * An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. - */ -export const ToolListChangedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/tools/list_changed'), - params: NotificationsParamsSchema.optional() -}); - /** * Callback type for list changed notifications. */ @@ -1521,238 +570,36 @@ export type ListChangedHandlers = { /** * Handler for tool list changes. */ - tools?: ListChangedOptions; - /** - * Handler for prompt list changes. - */ - prompts?: ListChangedOptions; - /** - * Handler for resource list changes. - */ - resources?: ListChangedOptions; -}; - -/* Logging */ -/** - * The severity of a log message. - */ -export const LoggingLevelSchema = z.enum(['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']); - -/** - * Parameters for a `logging/setLevel` request. - */ -export const SetLevelRequestParamsSchema = BaseRequestParamsSchema.extend({ - /** - * The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/logging/message. - */ - level: LoggingLevelSchema -}); -/** - * A request from the client to the server, to enable or adjust logging. - */ -export const SetLevelRequestSchema = RequestSchema.extend({ - method: z.literal('logging/setLevel'), - params: SetLevelRequestParamsSchema -}); - -/** - * Parameters for a `notifications/message` notification. - */ -export const LoggingMessageNotificationParamsSchema = NotificationsParamsSchema.extend({ - /** - * The severity of this log message. - */ - level: LoggingLevelSchema, - /** - * An optional name of the logger issuing this message. - */ - logger: z.string().optional(), - /** - * The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. - */ - data: z.unknown() -}); -/** - * Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically. - */ -export const LoggingMessageNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/message'), - params: LoggingMessageNotificationParamsSchema -}); - -/* Sampling */ -/** - * Hints to use for model selection. - */ -export const ModelHintSchema = z.object({ - /** - * A hint for a model name. - */ - name: z.string().optional() -}); - -/** - * The server's preferences for model selection, requested of the client during sampling. - */ -export const ModelPreferencesSchema = z.object({ - /** - * Optional hints to use for model selection. - */ - hints: z.array(ModelHintSchema).optional(), - /** - * How much to prioritize cost when selecting a model. - */ - costPriority: z.number().min(0).max(1).optional(), - /** - * How much to prioritize sampling speed (latency) when selecting a model. - */ - speedPriority: z.number().min(0).max(1).optional(), - /** - * How much to prioritize intelligence and capabilities when selecting a model. - */ - intelligencePriority: z.number().min(0).max(1).optional() -}); - -/** - * Controls tool usage behavior in sampling requests. - */ -export const ToolChoiceSchema = z.object({ - /** - * Controls when tools are used: - * - "auto": Model decides whether to use tools (default) - * - "required": Model MUST use at least one tool before completing - * - "none": Model MUST NOT use any tools - */ - mode: z.enum(['auto', 'required', 'none']).optional() -}); - -/** - * The result of a tool execution, provided by the user (server). - * Represents the outcome of invoking a tool requested via ToolUseContent. - */ -export const ToolResultContentSchema = z.object({ - type: z.literal('tool_result'), - toolUseId: z.string().describe('The unique identifier for the corresponding tool call.'), - content: z.array(ContentBlockSchema).default([]), - structuredContent: z.object({}).loose().optional(), - isError: z.boolean().optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * Basic content types for sampling responses (without tool use). - * Used for backwards-compatible CreateMessageResult when tools are not used. - */ -export const SamplingContentSchema = z.discriminatedUnion('type', [TextContentSchema, ImageContentSchema, AudioContentSchema]); - -/** - * Content block types allowed in sampling messages. - * This includes text, image, audio, tool use requests, and tool results. - */ -export const SamplingMessageContentBlockSchema = z.discriminatedUnion('type', [ - TextContentSchema, - ImageContentSchema, - AudioContentSchema, - ToolUseContentSchema, - ToolResultContentSchema -]); - -/** - * Describes a message issued to or received from an LLM API. - */ -export const SamplingMessageSchema = z.object({ - role: RoleSchema, - content: z.union([SamplingMessageContentBlockSchema, z.array(SamplingMessageContentBlockSchema)]), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * Parameters for a `sampling/createMessage` request. - */ -export const CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({ - messages: z.array(SamplingMessageSchema), - /** - * The server's preferences for which model to select. The client MAY modify or omit this request. - */ - modelPreferences: ModelPreferencesSchema.optional(), - /** - * An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. - */ - systemPrompt: z.string().optional(), - /** - * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. - * The client MAY ignore this request. - * - * Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client - * declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. - */ - includeContext: z.enum(['none', 'thisServer', 'allServers']).optional(), - temperature: z.number().optional(), - /** - * The requested maximum number of tokens to sample (to prevent runaway completions). - * - * The client MAY choose to sample fewer tokens than the requested maximum. - */ - maxTokens: z.number().int(), - stopSequences: z.array(z.string()).optional(), - /** - * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. - */ - metadata: AssertObjectSchema.optional(), + tools?: ListChangedOptions; /** - * Tools that the model may use during generation. - * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + * Handler for prompt list changes. */ - tools: z.array(ToolSchema).optional(), + prompts?: ListChangedOptions; /** - * Controls how the model uses tools. - * The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. - * Default is `{ mode: "auto" }`. + * Handler for resource list changes. */ - toolChoice: ToolChoiceSchema.optional() -}); + resources?: ListChangedOptions; +}; + +/* Logging */ + +/* Sampling */ + /** - * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. + * Basic content types for sampling responses (without tool use). + * Used for backwards-compatible CreateMessageResult when tools are not used. */ -export const CreateMessageRequestSchema = RequestSchema.extend({ - method: z.literal('sampling/createMessage'), - params: CreateMessageRequestParamsSchema -}); +export const SamplingContentSchema = z.discriminatedUnion('type', [TextContentSchema, ImageContentSchema, AudioContentSchema]); + +// SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, /** - * The client's response to a sampling/create_message request from the server. - * This is the backwards-compatible version that returns single content (no arrays). - * Used when the request does not include tools. + * The client's response to a sampling/create_message request (backwards-compatible version). + * Uses single content block without tool types for v1.x API compatibility. + * For tool use support, use CreateMessageResultWithToolsSchema instead. */ -export const CreateMessageResultSchema = ResultSchema.extend({ - /** - * The name of the model that generated the message. - */ - model: z.string(), - /** - * The reason why sampling stopped, if known. - * - * Standard values: - * - "endTurn": Natural end of the assistant's turn - * - "stopSequence": A stop sequence was encountered - * - "maxTokens": Maximum token limit was reached - * - * This field is an open string to allow for provider-specific stop reasons. - */ - stopReason: z.optional(z.enum(['endTurn', 'stopSequence', 'maxTokens']).or(z.string())), - role: RoleSchema, - /** - * Response content. Single content block (text, image, or audio). - */ +export const CreateMessageResultSchema = CreateMessageResultSpecSchema.omit({ content: true }).extend({ + /** Response content. Single block, basic types only (text/image/audio). */ content: SamplingContentSchema }); @@ -1785,306 +632,29 @@ export const CreateMessageResultWithToolsSchema = ResultSchema.extend({ }); /* Elicitation */ -/** - * Primitive schema definition for boolean fields. - */ -export const BooleanSchemaSchema = z.object({ - type: z.literal('boolean'), - title: z.string().optional(), - description: z.string().optional(), - default: z.boolean().optional() -}); - -/** - * Primitive schema definition for string fields. - */ -export const StringSchemaSchema = z.object({ - type: z.literal('string'), - title: z.string().optional(), - description: z.string().optional(), - minLength: z.number().optional(), - maxLength: z.number().optional(), - format: z.enum(['email', 'uri', 'date', 'date-time']).optional(), - default: z.string().optional() -}); - -/** - * Primitive schema definition for number fields. - */ -export const NumberSchemaSchema = z.object({ - type: z.enum(['number', 'integer']), - title: z.string().optional(), - description: z.string().optional(), - minimum: z.number().optional(), - maximum: z.number().optional(), - default: z.number().optional() -}); - -/** - * Schema for single-selection enumeration without display titles for options. - */ -export const UntitledSingleSelectEnumSchemaSchema = z.object({ - type: z.literal('string'), - title: z.string().optional(), - description: z.string().optional(), - enum: z.array(z.string()), - default: z.string().optional() -}); - -/** - * Schema for single-selection enumeration with display titles for each option. - */ -export const TitledSingleSelectEnumSchemaSchema = z.object({ - type: z.literal('string'), - title: z.string().optional(), - description: z.string().optional(), - oneOf: z.array( - z.object({ - const: z.string(), - title: z.string() - }) - ), - default: z.string().optional() -}); - -/** - * Use TitledSingleSelectEnumSchema instead. - * This interface will be removed in a future version. - */ -export const LegacyTitledEnumSchemaSchema = z.object({ - type: z.literal('string'), - title: z.string().optional(), - description: z.string().optional(), - enum: z.array(z.string()), - enumNames: z.array(z.string()).optional(), - default: z.string().optional() -}); - -// Combined single selection enumeration -export const SingleSelectEnumSchemaSchema = z.union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]); - -/** - * Schema for multiple-selection enumeration without display titles for options. - */ -export const UntitledMultiSelectEnumSchemaSchema = z.object({ - type: z.literal('array'), - title: z.string().optional(), - description: z.string().optional(), - minItems: z.number().optional(), - maxItems: z.number().optional(), - items: z.object({ - type: z.literal('string'), - enum: z.array(z.string()) - }), - default: z.array(z.string()).optional() -}); - -/** - * Schema for multiple-selection enumeration with display titles for each option. - */ -export const TitledMultiSelectEnumSchemaSchema = z.object({ - type: z.literal('array'), - title: z.string().optional(), - description: z.string().optional(), - minItems: z.number().optional(), - maxItems: z.number().optional(), - items: z.object({ - anyOf: z.array( - z.object({ - const: z.string(), - title: z.string() - }) - ) - }), - default: z.array(z.string()).optional() -}); - -/** - * Combined schema for multiple-selection enumeration - */ -export const MultiSelectEnumSchemaSchema = z.union([UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema]); - -/** - * Primitive schema definition for enum fields. - */ -export const EnumSchemaSchema = z.union([LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema]); - -/** - * Union of all primitive schema definitions. - */ -export const PrimitiveSchemaDefinitionSchema = z.union([EnumSchemaSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema]); - -/** - * Parameters for an `elicitation/create` request for form-based elicitation. - */ -export const ElicitRequestFormParamsSchema = TaskAugmentedRequestParamsSchema.extend({ - /** - * The elicitation mode. - * - * Optional for backward compatibility. Clients MUST treat missing mode as "form". - */ - mode: z.literal('form').optional(), - /** - * The message to present to the user describing what information is being requested. - */ - message: z.string(), - /** - * A restricted subset of JSON Schema. - * Only top-level properties are allowed, without nesting. - */ - requestedSchema: z.object({ - type: z.literal('object'), - properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), - required: z.array(z.string()).optional() - }) -}); - -/** - * Parameters for an `elicitation/create` request for URL-based elicitation. - */ -export const ElicitRequestURLParamsSchema = TaskAugmentedRequestParamsSchema.extend({ - /** - * The elicitation mode. - */ - mode: z.literal('url'), - /** - * The message to present to the user explaining why the interaction is needed. - */ - message: z.string(), - /** - * The ID of the elicitation, which must be unique within the context of the server. - * The client MUST treat this ID as an opaque value. - */ - elicitationId: z.string(), - /** - * The URL that the user should navigate to. - */ - url: z.string().url() -}); - -/** - * The parameters for a request to elicit additional information from the user via the client. - */ -export const ElicitRequestParamsSchema = z.union([ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema]); - -/** - * A request from the server to elicit user input via the client. - * The client should present the message and form fields to the user (form mode) - * or navigate to a URL (URL mode). - */ -export const ElicitRequestSchema = RequestSchema.extend({ - method: z.literal('elicitation/create'), - params: ElicitRequestParamsSchema -}); +// UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, +// LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, +// TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, /** * Parameters for a `notifications/elicitation/complete` notification. * * @category notifications/elicitation/complete */ -export const ElicitationCompleteNotificationParamsSchema = NotificationsParamsSchema.extend({ +export const ElicitationCompleteNotificationParamsSchema = NotificationParamsSchema.extend({ /** * The ID of the elicitation that completed. */ elicitationId: z.string() }); -/** - * A notification from the server to the client, informing it of a completion of an out-of-band elicitation request. - * - * @category notifications/elicitation/complete - */ -export const ElicitationCompleteNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/elicitation/complete'), - params: ElicitationCompleteNotificationParamsSchema -}); - -/** - * The client's response to an elicitation/create request from the server. - */ -export const ElicitResultSchema = ResultSchema.extend({ - /** - * The user action in response to the elicitation. - * - "accept": User submitted the form/confirmed the action - * - "decline": User explicitly decline the action - * - "cancel": User dismissed without making an explicit choice - */ - action: z.enum(['accept', 'decline', 'cancel']), - /** - * The submitted form data, only present when action is "accept". - * Contains values matching the requested schema. - * Per MCP spec, content is "typically omitted" for decline/cancel actions. - * We normalize null to undefined for leniency while maintaining type compatibility. - */ - content: z.preprocess( - val => (val === null ? undefined : val), - z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())])).optional() - ) -}); - /* Autocomplete */ -/** - * A reference to a resource or resource template definition. - */ -export const ResourceTemplateReferenceSchema = z.object({ - type: z.literal('ref/resource'), - /** - * The URI or URI template of the resource. - */ - uri: z.string() -}); /** * @deprecated Use ResourceTemplateReferenceSchema instead */ export const ResourceReferenceSchema = ResourceTemplateReferenceSchema; -/** - * Identifies a prompt. - */ -export const PromptReferenceSchema = z.object({ - type: z.literal('ref/prompt'), - /** - * The name of the prompt or prompt template - */ - name: z.string() -}); - -/** - * Parameters for a `completion/complete` request. - */ -export const CompleteRequestParamsSchema = BaseRequestParamsSchema.extend({ - ref: z.union([PromptReferenceSchema, ResourceTemplateReferenceSchema]), - /** - * The argument's information - */ - argument: z.object({ - /** - * The name of the argument - */ - name: z.string(), - /** - * The value of the argument to use for completion matching. - */ - value: z.string() - }), - context: z - .object({ - /** - * Previously-resolved variables in a URI template or prompt. - */ - arguments: z.record(z.string(), z.string()).optional() - }) - .optional() -}); -/** - * A request from the client to the server, to ask for completion options. - */ -export const CompleteRequestSchema = RequestSchema.extend({ - method: z.literal('completion/complete'), - params: CompleteRequestParamsSchema -}); - export function assertCompleteRequestPrompt(request: CompleteRequest): asserts request is CompleteRequestPrompt { if (request.params.ref.type !== 'ref/prompt') { throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`); @@ -2099,149 +669,9 @@ export function assertCompleteRequestResourceTemplate(request: CompleteRequest): void (request as CompleteRequestResourceTemplate); } -/** - * The server's response to a completion/complete request - */ -export const CompleteResultSchema = ResultSchema.extend({ - completion: z.looseObject({ - /** - * An array of completion values. Must not exceed 100 items. - */ - values: z.array(z.string()).max(100), - /** - * The total number of completion options available. This can exceed the number of values actually sent in the response. - */ - total: z.optional(z.number().int()), - /** - * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. - */ - hasMore: z.optional(z.boolean()) - }) -}); - /* Roots */ -/** - * Represents a root directory or file that the server can operate on. - */ -export const RootSchema = z.object({ - /** - * The URI identifying the root. This *must* start with file:// for now. - */ - uri: z.string().startsWith('file://'), - /** - * An optional name for the root. - */ - name: z.string().optional(), - - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.record(z.string(), z.unknown()).optional() -}); - -/** - * Sent from the server to request a list of root URIs from the client. - */ -export const ListRootsRequestSchema = RequestSchema.extend({ - method: z.literal('roots/list'), - params: BaseRequestParamsSchema.optional() -}); - -/** - * The client's response to a roots/list request from the server. - */ -export const ListRootsResultSchema = ResultSchema.extend({ - roots: z.array(RootSchema) -}); - -/** - * A notification from the client to the server, informing it that the list of roots has changed. - */ -export const RootsListChangedNotificationSchema = NotificationSchema.extend({ - method: z.literal('notifications/roots/list_changed'), - params: NotificationsParamsSchema.optional() -}); - -/* Client messages */ -export const ClientRequestSchema = z.union([ - PingRequestSchema, - InitializeRequestSchema, - CompleteRequestSchema, - SetLevelRequestSchema, - GetPromptRequestSchema, - ListPromptsRequestSchema, - ListResourcesRequestSchema, - ListResourceTemplatesRequestSchema, - ReadResourceRequestSchema, - SubscribeRequestSchema, - UnsubscribeRequestSchema, - CallToolRequestSchema, - ListToolsRequestSchema, - GetTaskRequestSchema, - GetTaskPayloadRequestSchema, - ListTasksRequestSchema, - CancelTaskRequestSchema -]); - -export const ClientNotificationSchema = z.union([ - CancelledNotificationSchema, - ProgressNotificationSchema, - InitializedNotificationSchema, - RootsListChangedNotificationSchema, - TaskStatusNotificationSchema -]); - -export const ClientResultSchema = z.union([ - EmptyResultSchema, - CreateMessageResultSchema, - CreateMessageResultWithToolsSchema, - ElicitResultSchema, - ListRootsResultSchema, - GetTaskResultSchema, - ListTasksResultSchema, - CreateTaskResultSchema -]); - -/* Server messages */ -export const ServerRequestSchema = z.union([ - PingRequestSchema, - CreateMessageRequestSchema, - ElicitRequestSchema, - ListRootsRequestSchema, - GetTaskRequestSchema, - GetTaskPayloadRequestSchema, - ListTasksRequestSchema, - CancelTaskRequestSchema -]); - -export const ServerNotificationSchema = z.union([ - CancelledNotificationSchema, - ProgressNotificationSchema, - LoggingMessageNotificationSchema, - ResourceUpdatedNotificationSchema, - ResourceListChangedNotificationSchema, - ToolListChangedNotificationSchema, - PromptListChangedNotificationSchema, - TaskStatusNotificationSchema, - ElicitationCompleteNotificationSchema -]); -export const ServerResultSchema = z.union([ - EmptyResultSchema, - InitializeResultSchema, - CompleteResultSchema, - GetPromptResultSchema, - ListPromptsResultSchema, - ListResourcesResultSchema, - ListResourceTemplatesResultSchema, - ReadResourceResultSchema, - CallToolResultSchema, - ListToolsResultSchema, - GetTaskResultSchema, - ListTasksResultSchema, - CreateTaskResultSchema -]); +/* Client/Server message types */ export class McpError extends Error { constructor( @@ -2343,151 +773,213 @@ export interface MessageExtraInfo { closeStandaloneSSEStream?: () => void; } -/* JSON-RPC types */ -export type ProgressToken = Infer; -export type Cursor = Infer; -export type Request = Infer; -export type TaskAugmentedRequestParams = Infer; -export type RequestMeta = Infer; -export type Notification = Infer; -export type Result = Infer; -export type RequestId = Infer; -export type JSONRPCRequest = Infer; -export type JSONRPCNotification = Infer; +// Import base types with aliases to avoid DOM collision, then re-export +import type { Request as _Request, Notification as _Notification, Result as _Result } from './generated/sdk.types.js'; + +// Re-export with original names +export type { _Request as Request, _Notification as Notification, _Result as Result }; + +/* Types re-exported from generated sdk.types.ts */ +export type { + // Union types for narrowing (Mcp prefix) + McpRequest, + McpNotification, + McpResult, + // Params types + RequestParams, + NotificationParams, + TaskAugmentedRequestParams, + // Primitives + ProgressToken, + Cursor, + RequestId, + // JSON-RPC wire types + JSONRPCRequest, + JSONRPCNotification, + JSONRPCResultResponse, + JSONRPCErrorResponse, + JSONRPCMessage, + // Empty result + EmptyResult, + // Cancellation + CancelledNotificationParams, + CancelledNotification, + // Base Metadata + Icon, + Icons, + BaseMetadata, + Annotations, + Role, + // Initialization + Implementation, + ClientCapabilities, + InitializeRequestParams, + InitializeRequest, + ServerCapabilities, + InitializeResult, + InitializedNotification, + // Ping + PingRequest, + // Progress notifications + ProgressNotificationParams, + ProgressNotification, + // Tasks + Task, + TaskStatus, + TaskMetadata, + RelatedTaskMetadata, + CreateTaskResult, + TaskStatusNotificationParams, + TaskStatusNotification, + GetTaskRequest, + GetTaskResult, + GetTaskPayloadRequest, + ListTasksRequest, + ListTasksResult, + CancelTaskRequest, + CancelTaskResult, + GetTaskPayloadResult, + // Pagination + PaginatedRequestParams, + PaginatedRequest, + PaginatedResult, + // Resources + ResourceContents, + TextResourceContents, + BlobResourceContents, + Resource, + ResourceTemplate, + ListResourcesRequest, + ListResourcesResult, + ListResourceTemplatesRequest, + ListResourceTemplatesResult, + ResourceRequestParams, + ReadResourceRequestParams, + ReadResourceRequest, + ReadResourceResult, + ResourceListChangedNotification, + SubscribeRequestParams, + SubscribeRequest, + UnsubscribeRequestParams, + UnsubscribeRequest, + ResourceUpdatedNotificationParams, + ResourceUpdatedNotification, + // Prompts + PromptArgument, + Prompt, + ListPromptsRequest, + ListPromptsResult, + GetPromptRequestParams, + GetPromptRequest, + TextContent, + ImageContent, + AudioContent, + ToolUseContent, + ToolResultContent, + EmbeddedResource, + ResourceLink, + ContentBlock, + PromptMessage, + GetPromptResult, + PromptListChangedNotification, + // Tools + ToolAnnotations, + ToolExecution, + Tool, + ListToolsRequest, + ListToolsResult, + CallToolRequestParams, + CallToolResult, + CallToolRequest, + ToolListChangedNotification, + // Logging + LoggingLevel, + SetLevelRequestParams, + SetLevelRequest, + LoggingMessageNotificationParams, + LoggingMessageNotification, + // Sampling + ToolChoice, + ModelHint, + ModelPreferences, + SamplingMessageContentBlock, + SamplingMessage, + CreateMessageRequestParams, + CreateMessageRequest, + CreateMessageResult, + // Elicitation + BooleanSchema, + StringSchema, + NumberSchema, + EnumSchema, + UntitledSingleSelectEnumSchema, + TitledSingleSelectEnumSchema, + LegacyTitledEnumSchema, + UntitledMultiSelectEnumSchema, + TitledMultiSelectEnumSchema, + SingleSelectEnumSchema, + MultiSelectEnumSchema, + PrimitiveSchemaDefinition, + ElicitRequestParams, + ElicitRequestFormParams, + ElicitRequestURLParams, + ElicitRequest, + ElicitationCompleteNotification, + ElicitResult, + // Autocomplete + ResourceTemplateReference, + PromptReference, + CompleteRequestParams, + CompleteRequest, + CompleteResult, + // Roots + Root, + ListRootsRequest, + ListRootsResult, + RootsListChangedNotification, + // Client messages + ClientRequest, + ClientNotification, + ClientResult, + // Server messages + ServerRequest, + ServerNotification, + ServerResult +} from './generated/sdk.types.js'; + +/** + * Request metadata - the _meta field type from RequestParams. + */ +export type RequestMeta = RequestParams['_meta']; + +/* SDK-specific types (not from spec, need Infer) */ + +// JSONRPCResponse is defined locally (union of result/error) export type JSONRPCResponse = Infer; -export type JSONRPCErrorResponse = Infer; -export type JSONRPCResultResponse = Infer; - -export type JSONRPCMessage = Infer; -export type RequestParams = Infer; -export type NotificationParams = Infer; - -/* Empty result */ -export type EmptyResult = Infer; - -/* Cancellation */ -export type CancelledNotificationParams = Infer; -export type CancelledNotification = Infer; - -/* Base Metadata */ -export type Icon = Infer; -export type Icons = Infer; -export type BaseMetadata = Infer; -export type Annotations = Infer; -export type Role = Infer; - -/* Initialization */ -export type Implementation = Infer; -export type ClientCapabilities = Infer; -export type InitializeRequestParams = Infer; -export type InitializeRequest = Infer; -export type ServerCapabilities = Infer; -export type InitializeResult = Infer; -export type InitializedNotification = Infer; -/* Ping */ -export type PingRequest = Infer; - -/* Progress notifications */ -export type Progress = Infer; -export type ProgressNotificationParams = Infer; -export type ProgressNotification = Infer; +// Progress type - derived from ProgressNotificationParams without progressToken +export type Progress = Omit; +// Type check: ensure Progress matches the inferred schema type +type _ProgressCheck = + Progress extends Infer ? (Infer extends Progress ? true : never) : never; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _progressTypeCheck: _ProgressCheck = true; -/* Tasks */ -export type Task = Infer; -export type TaskStatus = Infer; +// Task creation params (SDK-specific) export type TaskCreationParams = Infer; -export type TaskMetadata = Infer; -export type RelatedTaskMetadata = Infer; -export type CreateTaskResult = Infer; -export type TaskStatusNotificationParams = Infer; -export type TaskStatusNotification = Infer; -export type GetTaskRequest = Infer; -export type GetTaskResult = Infer; -export type GetTaskPayloadRequest = Infer; -export type ListTasksRequest = Infer; -export type ListTasksResult = Infer; -export type CancelTaskRequest = Infer; -export type CancelTaskResult = Infer; -export type GetTaskPayloadResult = Infer; - -/* Pagination */ -export type PaginatedRequestParams = Infer; -export type PaginatedRequest = Infer; -export type PaginatedResult = Infer; - -/* Resources */ -export type ResourceContents = Infer; -export type TextResourceContents = Infer; -export type BlobResourceContents = Infer; -export type Resource = Infer; -export type ResourceTemplate = Infer; -export type ListResourcesRequest = Infer; -export type ListResourcesResult = Infer; -export type ListResourceTemplatesRequest = Infer; -export type ListResourceTemplatesResult = Infer; -export type ResourceRequestParams = Infer; -export type ReadResourceRequestParams = Infer; -export type ReadResourceRequest = Infer; -export type ReadResourceResult = Infer; -export type ResourceListChangedNotification = Infer; -export type SubscribeRequestParams = Infer; -export type SubscribeRequest = Infer; -export type UnsubscribeRequestParams = Infer; -export type UnsubscribeRequest = Infer; -export type ResourceUpdatedNotificationParams = Infer; -export type ResourceUpdatedNotification = Infer; -/* Prompts */ -export type PromptArgument = Infer; -export type Prompt = Infer; -export type ListPromptsRequest = Infer; -export type ListPromptsResult = Infer; -export type GetPromptRequestParams = Infer; -export type GetPromptRequest = Infer; -export type TextContent = Infer; -export type ImageContent = Infer; -export type AudioContent = Infer; -export type ToolUseContent = Infer; -export type ToolResultContent = Infer; -export type EmbeddedResource = Infer; -export type ResourceLink = Infer; -export type ContentBlock = Infer; -export type PromptMessage = Infer; -export type GetPromptResult = Infer; -export type PromptListChangedNotification = Infer; - -/* Tools */ -export type ToolAnnotations = Infer; -export type ToolExecution = Infer; -export type Tool = Infer; -export type ListToolsRequest = Infer; -export type ListToolsResult = Infer; -export type CallToolRequestParams = Infer; -export type CallToolResult = Infer; +// Compatibility helper for older tool results export type CompatibilityCallToolResult = Infer; -export type CallToolRequest = Infer; -export type ToolListChangedNotification = Infer; - -/* Logging */ -export type LoggingLevel = Infer; -export type SetLevelRequestParams = Infer; -export type SetLevelRequest = Infer; -export type LoggingMessageNotificationParams = Infer; -export type LoggingMessageNotification = Infer; -/* Sampling */ -export type ToolChoice = Infer; -export type ModelHint = Infer; -export type ModelPreferences = Infer; +// Sampling content (SDK-specific discriminated union) export type SamplingContent = Infer; -export type SamplingMessageContentBlock = Infer; -export type SamplingMessage = Infer; -export type CreateMessageRequestParams = Infer; -export type CreateMessageRequest = Infer; -export type CreateMessageResult = Infer; + +// CreateMessageResult with tools (SDK extension) export type CreateMessageResultWithTools = Infer; +// Elicitation complete notification params (SDK extension) +export type ElicitationCompleteNotificationParams = Infer; + /** * CreateMessageRequestParams without tools - for backwards-compatible overload. * Excludes tools/toolChoice to indicate they should not be provided. @@ -2501,56 +993,12 @@ export interface CreateMessageRequestParamsWithTools extends CreateMessageReques tools: Tool[]; } -/* Elicitation */ -export type BooleanSchema = Infer; -export type StringSchema = Infer; -export type NumberSchema = Infer; - -export type EnumSchema = Infer; -export type UntitledSingleSelectEnumSchema = Infer; -export type TitledSingleSelectEnumSchema = Infer; -export type LegacyTitledEnumSchema = Infer; -export type UntitledMultiSelectEnumSchema = Infer; -export type TitledMultiSelectEnumSchema = Infer; -export type SingleSelectEnumSchema = Infer; -export type MultiSelectEnumSchema = Infer; - -export type PrimitiveSchemaDefinition = Infer; -export type ElicitRequestParams = Infer; -export type ElicitRequestFormParams = Infer; -export type ElicitRequestURLParams = Infer; -export type ElicitRequest = Infer; -export type ElicitationCompleteNotificationParams = Infer; -export type ElicitationCompleteNotification = Infer; -export type ElicitResult = Infer; - -/* Autocomplete */ -export type ResourceTemplateReference = Infer; /** * @deprecated Use ResourceTemplateReference instead */ export type ResourceReference = ResourceTemplateReference; -export type PromptReference = Infer; -export type CompleteRequestParams = Infer; -export type CompleteRequest = Infer; + export type CompleteRequestResourceTemplate = ExpandRecursively< CompleteRequest & { params: CompleteRequestParams & { ref: ResourceTemplateReference } } >; export type CompleteRequestPrompt = ExpandRecursively; -export type CompleteResult = Infer; - -/* Roots */ -export type Root = Infer; -export type ListRootsRequest = Infer; -export type ListRootsResult = Infer; -export type RootsListChangedNotification = Infer; - -/* Client messages */ -export type ClientRequest = Infer; -export type ClientNotification = Infer; -export type ClientResult = Infer; - -/* Server messages */ -export type ServerRequest = Infer; -export type ServerNotification = Infer; -export type ServerResult = Infer; diff --git a/test/client/index.test.ts b/test/client/index.test.ts index 9735eb2ba..d0b292735 100644 --- a/test/client/index.test.ts +++ b/test/client/index.test.ts @@ -6,6 +6,7 @@ import { RequestSchema, NotificationSchema, ResultSchema, + Result, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, InitializeRequestSchema, @@ -17,10 +18,12 @@ import { CallToolResultSchema, CreateMessageRequestSchema, ElicitRequestSchema, + ElicitResult, ElicitResultSchema, ListRootsRequestSchema, ErrorCode, McpError, + CreateTaskResult, CreateTaskResultSchema, Tool, Prompt, @@ -211,7 +214,7 @@ test('should initialize with matching protocol version', async () => { version: '1.0' }, instructions: 'test instructions' - } + } as Result }); } return Promise.resolve(); @@ -269,7 +272,7 @@ test('should initialize with supported older protocol version', async () => { name: 'test', version: '1.0' } - } + } as Result }); } return Promise.resolve(); @@ -319,7 +322,7 @@ test('should reject unsupported protocol version', async () => { name: 'test', version: '1.0' } - } + } as Result }); } return Promise.resolve(); @@ -885,7 +888,7 @@ test('should reject form-mode elicitation when client only supports URL mode', a name: 'test-server', version: '1.0.0' } - } + } as Result }); } else if (message.method === 'notifications/initialized') { // ignore @@ -1030,7 +1033,7 @@ test('should reject URL-mode elicitation when client only supports form mode', a name: 'test-server', version: '1.0.0' } - } + } as Result }); } else if (message.method === 'notifications/initialized') { // ignore @@ -1830,6 +1833,7 @@ describe('outputSchema validation', () => { server.setRequestHandler(CallToolRequestSchema, async request => { if (request.params.name === 'test-tool') { return { + content: [], structuredContent: { result: 'success', count: 42 } }; } @@ -1842,20 +1846,7 @@ describe('outputSchema validation', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - }, - tasks: { - get: true, - list: {}, - result: true - } - } - } - } + capabilities: {} } ); @@ -1923,6 +1914,7 @@ describe('outputSchema validation', () => { if (request.params.name === 'test-tool') { // Return invalid structured content (count is string instead of number) return { + content: [], structuredContent: { result: 'success', count: 'not a number' } }; } @@ -1935,20 +1927,7 @@ describe('outputSchema validation', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - }, - tasks: { - get: true, - list: {}, - result: true - } - } - } - } + capabilities: {} } ); @@ -2025,20 +2004,7 @@ describe('outputSchema validation', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - }, - tasks: { - get: true, - list: {}, - result: true - } - } - } - } + capabilities: {} } ); @@ -2111,20 +2077,7 @@ describe('outputSchema validation', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - }, - tasks: { - get: true, - list: {}, - result: true - } - } - } - } + capabilities: {} } ); @@ -2204,6 +2157,7 @@ describe('outputSchema validation', () => { server.setRequestHandler(CallToolRequestSchema, async request => { if (request.params.name === 'complex-tool') { return { + content: [], structuredContent: { name: 'John Doe', age: 30, @@ -2224,20 +2178,7 @@ describe('outputSchema validation', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - }, - tasks: { - get: true, - list: {}, - result: true - } - } - } - } + capabilities: {} } ); @@ -2307,6 +2248,7 @@ describe('outputSchema validation', () => { if (request.params.name === 'strict-tool') { // Return structured content with extra property return { + content: [], structuredContent: { name: 'John', extraField: 'not allowed' @@ -2381,7 +2323,7 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: 'Tool executed successfully!' }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -2457,7 +2399,7 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: 'Success!' }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -2510,8 +2452,7 @@ describe('Task-based execution', () => { tasks: { requests: { tools: { - call: {}, - list: {} + call: {} } } } @@ -2534,7 +2475,7 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: 'Result data!' }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -2615,7 +2556,7 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: 'Success!' }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -2705,9 +2646,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { - const result = { - action: 'accept', + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { + const result: ElicitResult = { + action: 'accept' as const, content: { username: 'list-user' } }; @@ -2731,15 +2672,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - elicitation: { - create: {} - } - } - } - } + capabilities: {} } ); @@ -2798,9 +2731,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { - const result = { - action: 'accept', + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { + const result: ElicitResult = { + action: 'accept' as const, content: { username: 'list-user' } }; @@ -2824,15 +2757,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - elicitation: { - create: {} - } - } - } - } + capabilities: {} } ); @@ -2890,9 +2815,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { - const result = { - action: 'accept', + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { + const result: ElicitResult = { + action: 'accept' as const, content: { username: 'result-user' } }; @@ -2916,15 +2841,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - elicitation: { - create: {} - } - } - } - } + capabilities: {} } ); @@ -2981,9 +2898,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { - const result = { - action: 'accept', + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { + const result: ElicitResult = { + action: 'accept' as const, content: { username: 'list-user' } }; @@ -3007,15 +2924,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - elicitation: { - create: {} - } - } - } - } + capabilities: {} } ); @@ -3100,7 +3009,7 @@ describe('Task-based execution', () => { const result = { content: [{ type: 'text', text: `Result for ${id || 'unknown'}` }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -3125,15 +3034,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - } - } - } - } + capabilities: {} } ); @@ -3212,15 +3113,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - } - } - } - } + capabilities: {} } ); @@ -3259,15 +3152,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - tools: { - call: {} - } - } - } - } + capabilities: {} } ); @@ -3311,15 +3196,7 @@ describe('Task-based execution', () => { version: '1.0.0' }, { - capabilities: { - tasks: { - requests: { - elicitation: { - create: {} - } - } - } - } + capabilities: {} } ); @@ -3368,7 +3245,7 @@ test('should respect server task capabilities', async () => { const result = { content: [{ type: 'text', text: 'Success!' }] - }; + } as Result; await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); return { task }; @@ -3675,6 +3552,7 @@ test('callToolStream() should yield error when structuredContent does not match server.setRequestHandler(CallToolRequestSchema, async () => { // Return invalid structured content (count is string instead of number) return { + content: [], structuredContent: { result: 'success', count: 'not a number' } }; }); @@ -3900,6 +3778,7 @@ test('callToolStream() should handle complex JSON schema validation', async () = server.setRequestHandler(CallToolRequestSchema, async () => { return { + content: [], structuredContent: { name: 'John Doe', age: 30, @@ -3984,6 +3863,7 @@ test('callToolStream() should yield error with additional properties when not al server.setRequestHandler(CallToolRequestSchema, async () => { return { + content: [], structuredContent: { name: 'John', extraField: 'not allowed' diff --git a/test/client/stdio.test.ts b/test/client/stdio.test.ts index 52a871ee1..4667721d7 100644 --- a/test/client/stdio.test.ts +++ b/test/client/stdio.test.ts @@ -52,7 +52,9 @@ test('should read messages', async () => { client.onmessage = message => { readMessages.push(message); - if (JSON.stringify(message) === JSON.stringify(messages[1])) { + // Compare message content instead of JSON.stringify (Zod reorders keys) + const msg1 = messages[1]; + if ('method' in message && 'method' in msg1 && message.method === msg1.method && message.jsonrpc === msg1.jsonrpc) { resolve(); } }; diff --git a/test/client/streamableHttp.test.ts b/test/client/streamableHttp.test.ts index 52c8f1074..6c46da4c7 100644 --- a/test/client/streamableHttp.test.ts +++ b/test/client/streamableHttp.test.ts @@ -1,6 +1,6 @@ import { StartSSEOptions, StreamableHTTPClientTransport, StreamableHTTPReconnectionOptions } from '../../src/client/streamableHttp.js'; import { OAuthClientProvider, UnauthorizedError } from '../../src/client/auth.js'; -import { JSONRPCMessage, JSONRPCRequest } from '../../src/types.js'; +import { JSONRPCMessage, JSONRPCRequest, type Result } from '../../src/types.js'; import { InvalidClientError, InvalidGrantError, UnauthorizedClientError } from '../../src/server/auth/errors.js'; import { type Mock, type Mocked } from 'vitest'; @@ -223,7 +223,7 @@ describe('StreamableHTTPClientTransport', () => { const responseMessage: JSONRPCMessage = { jsonrpc: '2.0', - result: { success: true }, + result: { success: true } as Result, id: 'test-id' }; diff --git a/test/experimental/tasks/stores/in-memory.test.ts b/test/experimental/tasks/stores/in-memory.test.ts index ceef6c6d8..cfb9dd956 100644 --- a/test/experimental/tasks/stores/in-memory.test.ts +++ b/test/experimental/tasks/stores/in-memory.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { InMemoryTaskStore, InMemoryTaskMessageQueue } from '../../../../src/experimental/tasks/stores/in-memory.js'; -import { TaskCreationParams, Request } from '../../../../src/types.js'; +import { TaskCreationParams, Request, Notification, Result, CallToolResult } from '../../../../src/types.js'; import { QueuedMessage } from '../../../../src/experimental/tasks/interfaces.js'; describe('InMemoryTaskStore', () => { @@ -19,12 +19,12 @@ describe('InMemoryTaskStore', () => { const taskParams: TaskCreationParams = { ttl: 60000 }; - const request: Request = { - method: 'tools/call', - params: { name: 'test-tool' } + const request = { + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; - const task = await store.createTask(taskParams, 123, request); + const task = await store.createTask(taskParams, 123, request as any); expect(task).toBeDefined(); expect(task.taskId).toBeDefined(); @@ -39,12 +39,12 @@ describe('InMemoryTaskStore', () => { it('should create task without ttl', async () => { const taskParams: TaskCreationParams = {}; - const request: Request = { - method: 'tools/call', - params: {} + const request = { + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; - const task = await store.createTask(taskParams, 456, request); + const task = await store.createTask(taskParams, 456, request as any); expect(task).toBeDefined(); expect(task.ttl).toBeNull(); @@ -52,13 +52,13 @@ describe('InMemoryTaskStore', () => { it('should generate unique taskIds', async () => { const taskParams: TaskCreationParams = {}; - const request: Request = { - method: 'tools/call', - params: {} + const request = { + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; - const task1 = await store.createTask(taskParams, 789, request); - const task2 = await store.createTask(taskParams, 790, request); + const task1 = await store.createTask(taskParams, 789, request as any); + const task2 = await store.createTask(taskParams, 790, request as any); expect(task1.taskId).not.toBe(task2.taskId); }); @@ -72,12 +72,12 @@ describe('InMemoryTaskStore', () => { it('should return task state', async () => { const taskParams: TaskCreationParams = {}; - const request: Request = { - method: 'tools/call', - params: {} + const request = { + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; - const createdTask = await store.createTask(taskParams, 111, request); + const createdTask = await store.createTask(taskParams, 111, request as any); await store.updateTaskStatus(createdTask.taskId, 'working'); const task = await store.getTask(createdTask.taskId); @@ -92,9 +92,9 @@ describe('InMemoryTaskStore', () => { beforeEach(async () => { const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 222, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); taskId = createdTask.taskId; }); @@ -223,14 +223,14 @@ describe('InMemoryTaskStore', () => { ttl: 60000 }; const createdTask = await store.createTask(taskParams, 333, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); taskId = createdTask.taskId; }); it('should store task result and set status to completed', async () => { - const result = { + const result: CallToolResult = { content: [{ type: 'text' as const, text: 'Success!' }] }; @@ -249,13 +249,13 @@ describe('InMemoryTaskStore', () => { it('should reject storing result for task already in completed status', async () => { // First complete the task - const firstResult = { + const firstResult: CallToolResult = { content: [{ type: 'text' as const, text: 'First result' }] }; await store.storeTaskResult(taskId, 'completed', firstResult); // Try to store result again (should fail) - const secondResult = { + const secondResult: CallToolResult = { content: [{ type: 'text' as const, text: 'Second result' }] }; @@ -263,7 +263,7 @@ describe('InMemoryTaskStore', () => { }); it('should store result with failed status', async () => { - const result = { + const result: CallToolResult = { content: [{ type: 'text' as const, text: 'Error details' }], isError: true }; @@ -279,14 +279,14 @@ describe('InMemoryTaskStore', () => { it('should reject storing result for task already in failed status', async () => { // First fail the task - const firstResult = { + const firstResult: CallToolResult = { content: [{ type: 'text' as const, text: 'First error' }], isError: true }; await store.storeTaskResult(taskId, 'failed', firstResult); // Try to store result again (should fail) - const secondResult = { + const secondResult: CallToolResult = { content: [{ type: 'text' as const, text: 'Second error' }], isError: true }; @@ -299,7 +299,7 @@ describe('InMemoryTaskStore', () => { await store.updateTaskStatus(taskId, 'cancelled'); // Try to store result (should fail) - const result = { + const result: CallToolResult = { content: [{ type: 'text' as const, text: 'Cancellation result' }] }; @@ -309,7 +309,7 @@ describe('InMemoryTaskStore', () => { it('should allow storing result from input_required status', async () => { await store.updateTaskStatus(taskId, 'input_required'); - const result = { + const result: CallToolResult = { content: [{ type: 'text' as const, text: 'Success!' }] }; @@ -328,9 +328,9 @@ describe('InMemoryTaskStore', () => { it('should throw if task has no result stored', async () => { const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 444, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await expect(store.getTaskResult(createdTask.taskId)).rejects.toThrow(`Task ${createdTask.taskId} has no result stored`); }); @@ -338,11 +338,11 @@ describe('InMemoryTaskStore', () => { it('should return stored result', async () => { const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 555, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); - const result = { + const result: CallToolResult = { content: [{ type: 'text' as const, text: 'Result data' }] }; await store.storeTaskResult(createdTask.taskId, 'completed', result); @@ -366,9 +366,9 @@ describe('InMemoryTaskStore', () => { ttl: 1000 }; const createdTask = await store.createTask(taskParams, 666, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // Task should exist initially let task = await store.getTask(createdTask.taskId); @@ -387,9 +387,9 @@ describe('InMemoryTaskStore', () => { ttl: 1000 }; const createdTask = await store.createTask(taskParams, 777, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // Fast-forward 500ms vi.advanceTimersByTime(500); @@ -397,7 +397,7 @@ describe('InMemoryTaskStore', () => { // Store result (should reset timer) await store.storeTaskResult(createdTask.taskId, 'completed', { content: [{ type: 'text' as const, text: 'Done' }] - }); + } as CallToolResult); // Fast-forward another 500ms (total 1000ms since creation, but timer was reset) vi.advanceTimersByTime(500); @@ -417,9 +417,9 @@ describe('InMemoryTaskStore', () => { it('should not cleanup tasks without ttl', async () => { const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 888, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // Fast-forward a long time vi.advanceTimersByTime(100000); @@ -434,9 +434,9 @@ describe('InMemoryTaskStore', () => { ttl: 1000 }; const createdTask = await store.createTask(taskParams, 999, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // Task in non-terminal state, fast-forward vi.advanceTimersByTime(1001); @@ -450,9 +450,9 @@ describe('InMemoryTaskStore', () => { ttl: 2000 }; const createdTask2 = await store.createTask(taskParams2, 1000, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // Update to terminal state await store.updateTaskStatus(createdTask2.taskId, 'completed'); @@ -474,9 +474,9 @@ describe('InMemoryTaskStore', () => { ttl: requestedTtl }; const createdTask = await store.createTask(taskParams, 1111, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // The returned task should include the actual TTL that will be used expect(createdTask.ttl).toBe(requestedTtl); @@ -493,9 +493,9 @@ describe('InMemoryTaskStore', () => { ttl: null }; const createdTask = await store.createTask(taskParams, 2222, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); // The returned task should have null TTL expect(createdTask.ttl).toBeNull(); @@ -515,25 +515,25 @@ describe('InMemoryTaskStore', () => { // Create tasks in different statuses const workingTask = await store.createTask(taskParams, 3333, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); const completedTask = await store.createTask(taskParams, 4444, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.storeTaskResult(completedTask.taskId, 'completed', { content: [{ type: 'text' as const, text: 'Done' }] - }); + } as CallToolResult); const failedTask = await store.createTask(taskParams, 5555, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.storeTaskResult(failedTask.taskId, 'failed', { content: [{ type: 'text' as const, text: 'Error' }] - }); + } as CallToolResult); // Fast-forward past TTL vi.advanceTimersByTime(1001); @@ -548,17 +548,17 @@ describe('InMemoryTaskStore', () => { describe('getAllTasks', () => { it('should return all tasks', async () => { await store.createTask({}, 1, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.createTask({}, 2, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.createTask({}, 3, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); const tasks = store.getAllTasks(); expect(tasks).toHaveLength(3); @@ -582,17 +582,17 @@ describe('InMemoryTaskStore', () => { it('should return all tasks when less than page size', async () => { await store.createTask({}, 1, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.createTask({}, 2, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.createTask({}, 3, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); const result = await store.listTasks(); expect(result.tasks).toHaveLength(3); @@ -603,9 +603,9 @@ describe('InMemoryTaskStore', () => { // Create 15 tasks (page size is 10) for (let i = 1; i <= 15; i++) { await store.createTask({}, i, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); } // Get first page @@ -621,9 +621,9 @@ describe('InMemoryTaskStore', () => { it('should throw error for invalid cursor', async () => { await store.createTask({}, 1, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await expect(store.listTasks('non-existent-cursor')).rejects.toThrow('Invalid cursor: non-existent-cursor'); }); @@ -632,9 +632,9 @@ describe('InMemoryTaskStore', () => { // Create 5 tasks for (let i = 1; i <= 5; i++) { await store.createTask({}, i, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); } // Get first 3 tasks @@ -649,13 +649,13 @@ describe('InMemoryTaskStore', () => { describe('cleanup', () => { it('should clear all timers and tasks', async () => { await store.createTask({ ttl: 1000 }, 1, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); await store.createTask({ ttl: 2000 }, 2, { - method: 'tools/call', - params: {} - }); + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any); expect(store.getAllTasks()).toHaveLength(2); @@ -680,7 +680,7 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'tools/call', + method: 'tools/call' as const, params: { name: 'test-tool', arguments: {} } }, timestamp: Date.now() @@ -697,7 +697,7 @@ describe('InMemoryTaskMessageQueue', () => { type: 'notification', message: { jsonrpc: '2.0', - method: 'notifications/progress', + method: 'notifications/progress' as const, params: { progress: 50, total: 100 } }, timestamp: Date.now() @@ -715,7 +715,7 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 42, - result: { content: [{ type: 'text', text: 'Success' }] } + result: { content: [{ type: 'text', text: 'Success' }] } as Result }, timestamp: Date.now() }; @@ -737,9 +737,9 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'tools/call', - params: {} - }, + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any, timestamp: 1000 }; @@ -747,7 +747,7 @@ describe('InMemoryTaskMessageQueue', () => { type: 'notification', message: { jsonrpc: '2.0', - method: 'notifications/progress', + method: 'notifications/progress' as const, params: {} }, timestamp: 2000 @@ -781,9 +781,9 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'tools/call', - params: {} - }, + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } + } as any, timestamp: 1000 }; @@ -801,7 +801,7 @@ describe('InMemoryTaskMessageQueue', () => { type: 'notification', message: { jsonrpc: '2.0', - method: 'notifications/progress', + method: 'notifications/progress' as const, params: {} }, timestamp: 3000 @@ -830,7 +830,7 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'test', + method: 'test' as const, params: {} }, timestamp: Date.now() @@ -851,7 +851,7 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'test', + method: 'test' as const, params: {} }, timestamp: Date.now() @@ -885,7 +885,7 @@ describe('InMemoryTaskMessageQueue', () => { message: { jsonrpc: '2.0', id: 1, - method: 'test1', + method: 'test1' as const, params: {} }, timestamp: 1000 diff --git a/test/generated/spec.schemas.compare.test.ts b/test/generated/spec.schemas.compare.test.ts new file mode 100644 index 000000000..ffb2fedae --- /dev/null +++ b/test/generated/spec.schemas.compare.test.ts @@ -0,0 +1,234 @@ +/** + * Tests that verify generated schemas match the manually-defined schemas in types.ts. + * + * This ensures the ts-to-zod generation produces schemas equivalent to the + * hand-crafted ones, catching any drift between the two. + */ +import { describe, it, expect } from 'vitest'; +import { z } from 'zod/v4'; + +// Import generated schemas (from pre-processed types with SDK-compatible hierarchy) +import * as generated from '../../src/generated/sdk.schemas.js'; + +// Import manual schemas from types.ts +import * as manual from '../../src/types.js'; + +/** + * Helper to compare two Zod schemas by checking they accept/reject the same values. + * We test with valid examples and ensure both schemas have the same structure. + */ +function schemasAreEquivalent( + name: string, + genSchema: z.ZodType, + manSchema: z.ZodType, + testCases: { valid: unknown[]; invalid: unknown[] } +): void { + describe(name, () => { + for (const valid of testCases.valid) { + it(`should accept valid value: ${JSON.stringify(valid).slice(0, 50)}`, () => { + const genResult = genSchema.safeParse(valid); + const manResult = manSchema.safeParse(valid); + expect(genResult.success).toBe(true); + expect(manResult.success).toBe(true); + }); + } + + for (const invalid of testCases.invalid) { + const label = JSON.stringify(invalid)?.slice(0, 50) ?? String(invalid); + it(`should reject invalid value: ${label}`, () => { + const genResult = genSchema.safeParse(invalid); + const manResult = manSchema.safeParse(invalid); + // Both should reject, though error messages may differ + expect(genResult.success).toBe(manResult.success); + }); + } + }); +} + +describe('Generated vs Manual Schema Equivalence', () => { + // Test primitive/simple schemas + schemasAreEquivalent('ProgressTokenSchema', generated.ProgressTokenSchema, manual.ProgressTokenSchema, { + valid: ['token123', 42, 0, 'abc'], + invalid: [null, undefined, {}, [], true] + }); + + schemasAreEquivalent('CursorSchema', generated.CursorSchema, manual.CursorSchema, { + valid: ['cursor123', '', 'abc'], + invalid: [null, undefined, 42, {}, []] + }); + + schemasAreEquivalent('RequestIdSchema', generated.RequestIdSchema, manual.RequestIdSchema, { + valid: ['id123', 42, 0, 'abc'], + invalid: [null, undefined, {}, [], true] + }); + + // Test object schemas + schemasAreEquivalent('ImplementationSchema', generated.ImplementationSchema, manual.ImplementationSchema, { + valid: [ + { name: 'test', version: '1.0.0' }, + { name: 'test', version: '1.0.0', title: 'Test Title' } + ], + invalid: [ + null, + {}, + { name: 'test' }, // missing version + { version: '1.0.0' } // missing name + ] + }); + + schemasAreEquivalent('ToolSchema', generated.ToolSchema, manual.ToolSchema, { + valid: [ + { name: 'myTool', inputSchema: { type: 'object' } }, + { name: 'myTool', inputSchema: { type: 'object' }, description: 'A tool' } + ], + invalid: [ + null, + {}, + { name: 'myTool' }, // missing inputSchema + { inputSchema: { type: 'object' } } // missing name + ] + }); + + schemasAreEquivalent('ResourceSchema', generated.ResourceSchema, manual.ResourceSchema, { + valid: [ + { uri: 'file:///test.txt', name: 'test.txt' }, + { uri: 'file:///test.txt', name: 'test.txt', description: 'A file', mimeType: 'text/plain' } + ], + invalid: [ + null, + {}, + { uri: 'file:///test.txt' }, // missing name + { name: 'test.txt' } // missing uri + ] + }); + + schemasAreEquivalent('PromptSchema', generated.PromptSchema, manual.PromptSchema, { + valid: [{ name: 'myPrompt' }, { name: 'myPrompt', description: 'A prompt', arguments: [] }], + invalid: [ + null, + {}, + { description: 'A prompt' } // missing name + ] + }); + + // Test content schemas + schemasAreEquivalent('TextContentSchema', generated.TextContentSchema, manual.TextContentSchema, { + valid: [{ type: 'text', text: 'Hello' }], + invalid: [ + null, + {}, + { type: 'text' }, // missing text + { text: 'Hello' }, // missing type + { type: 'image', text: 'Hello' } // wrong type + ] + }); + + schemasAreEquivalent('ImageContentSchema', generated.ImageContentSchema, manual.ImageContentSchema, { + valid: [{ type: 'image', data: 'base64data', mimeType: 'image/png' }], + invalid: [ + null, + {}, + { type: 'image', data: 'base64data' }, // missing mimeType + { type: 'text', data: 'base64data', mimeType: 'image/png' } // wrong type + ] + }); + + // Test JSON-RPC request schemas (now with proper jsonrpc literal after post-processing) + schemasAreEquivalent('JSONRPCRequestSchema', generated.JSONRPCRequestSchema, manual.JSONRPCRequestSchema, { + valid: [ + { jsonrpc: '2.0', id: 1, method: 'test' }, + { jsonrpc: '2.0', id: 'abc', method: 'test', params: {} } + ], + invalid: [ + null, + {}, + { jsonrpc: '1.0', id: 1, method: 'test' }, // wrong jsonrpc version + { id: 1, method: 'test' } // missing jsonrpc + ] + }); + + schemasAreEquivalent('InitializeRequestSchema', generated.InitializeRequestSchema, manual.InitializeRequestSchema, { + valid: [ + { + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test', version: '1.0.0' } + } + } + ], + invalid: [ + null, + {}, + { jsonrpc: '2.0', id: 1, method: 'initialize' }, // missing params + { jsonrpc: '2.0', id: 1, method: 'other', params: {} } // wrong method + ] + }); + + schemasAreEquivalent('CallToolRequestSchema', generated.CallToolRequestSchema, manual.CallToolRequestSchema, { + valid: [ + { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'myTool' } }, + { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'myTool', arguments: { foo: 'bar' } } } + ], + invalid: [ + null, + {}, + { jsonrpc: '2.0', id: 1, method: 'tools/call' }, // missing params + { jsonrpc: '2.0', id: 1, method: 'tools/call', params: {} } // missing name in params + ] + }); + + // Also test request params schemas + schemasAreEquivalent('InitializeRequestParamsSchema', generated.InitializeRequestParamsSchema, manual.InitializeRequestParamsSchema, { + valid: [ + { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'test', version: '1.0.0' } + } + ], + invalid: [ + null, + {}, + { protocolVersion: '2024-11-05' } // missing capabilities and clientInfo + ] + }); + + schemasAreEquivalent('CallToolRequestParamsSchema', generated.CallToolRequestParamsSchema, manual.CallToolRequestParamsSchema, { + valid: [{ name: 'myTool' }, { name: 'myTool', arguments: { foo: 'bar' } }], + invalid: [ + null, + {}, + { arguments: { foo: 'bar' } } // missing name + ] + }); + + // Test notification schemas (now SDK-compatible, extending NotificationSchema) + schemasAreEquivalent('CancelledNotificationSchema', generated.CancelledNotificationSchema, manual.CancelledNotificationSchema, { + valid: [ + { method: 'notifications/cancelled', params: {} }, + { method: 'notifications/cancelled', params: { requestId: '123', reason: 'timeout' } } + ], + invalid: [ + null, + {}, + { method: 'notifications/cancelled' }, // missing params + { method: 'other', params: {} } // wrong method + ] + }); + + schemasAreEquivalent('ProgressNotificationSchema', generated.ProgressNotificationSchema, manual.ProgressNotificationSchema, { + valid: [ + { method: 'notifications/progress', params: { progressToken: 'token', progress: 50 } }, + { method: 'notifications/progress', params: { progressToken: 'token', progress: 50, total: 100 } } + ], + invalid: [ + null, + {}, + { method: 'notifications/progress' } // missing params + ] + }); +}); diff --git a/test/helpers/mcp.ts b/test/helpers/mcp.ts index 6cd08fdf0..b8758ac33 100644 --- a/test/helpers/mcp.ts +++ b/test/helpers/mcp.ts @@ -28,11 +28,7 @@ export async function createInMemoryTaskEnvironment(options?: { capabilities: options?.clientCapabilities ?? { tasks: { list: {}, - requests: { - tools: { - call: {} - } - } + cancel: {} } } } diff --git a/test/integration-tests/taskLifecycle.test.ts b/test/integration-tests/taskLifecycle.test.ts index 629a61b66..3a354c8e8 100644 --- a/test/integration-tests/taskLifecycle.test.ts +++ b/test/integration-tests/taskLifecycle.test.ts @@ -5,7 +5,9 @@ import { StreamableHTTPClientTransport } from '../../src/client/streamableHttp.j import { McpServer } from '../../src/server/mcp.js'; import { StreamableHTTPServerTransport } from '../../src/server/streamableHttp.js'; import { + CallToolResult, CallToolResultSchema, + CancelTaskResultSchema, CreateTaskResultSchema, ElicitRequestSchema, ElicitResultSchema, @@ -78,11 +80,11 @@ describe('Task Lifecycle Integration Tests', () => { await extra.taskStore.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: 'Task failed as requested' }], isError: true - }); + } as CallToolResult); } else { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Completed after ${duration}ms` }] - }); + } as CallToolResult); } } catch { // Task may have been cleaned up if test ended @@ -155,7 +157,7 @@ describe('Task Lifecycle Integration Tests', () => { try { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Hello, ${name}!` }] - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -164,7 +166,7 @@ describe('Task Lifecycle Integration Tests', () => { try { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Hello, ${userName}!` }] - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -262,7 +264,7 @@ describe('Task Lifecycle Integration Tests', () => { // Verify result is stored const result = await taskStore.getTaskResult(taskId); expect(result).toBeDefined(); - expect(result.content).toEqual([{ type: 'text', text: 'Completed after 500ms' }]); + expect((result as { content: unknown }).content).toEqual([{ type: 'text', text: 'Completed after 500ms' }]); await transport.close(); }); @@ -304,8 +306,8 @@ describe('Task Lifecycle Integration Tests', () => { // Verify error result is stored const result = await taskStore.getTaskResult(taskId); - expect(result.content).toEqual([{ type: 'text', text: 'Task failed as requested' }]); - expect(result.isError).toBe(true); + expect((result as { content: unknown }).content).toEqual([{ type: 'text', text: 'Task failed as requested' }]); + expect((result as { isError?: boolean }).isError).toBe(true); await transport.close(); }); @@ -462,7 +464,7 @@ describe('Task Lifecycle Integration Tests', () => { try { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Received responses: ${responses.join(', ')}` }] - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -1040,7 +1042,7 @@ describe('Task Lifecycle Integration Tests', () => { method: 'tasks/cancel', params: { taskId } }, - z.object({ _meta: z.record(z.unknown()).optional() }) + CancelTaskResultSchema ); // Verify task is cancelled @@ -1156,7 +1158,7 @@ describe('Task Lifecycle Integration Tests', () => { try { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: `Received all responses: ${responses.join(', ')}` }] - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -1166,7 +1168,7 @@ describe('Task Lifecycle Integration Tests', () => { await extra.taskStore.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: `Error: ${error}` }], isError: true - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -1365,7 +1367,7 @@ describe('Task Lifecycle Integration Tests', () => { try { await extra.taskStore.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: 'Task completed quickly' }] - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } @@ -1375,7 +1377,7 @@ describe('Task Lifecycle Integration Tests', () => { await extra.taskStore.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text', text: `Error: ${error}` }], isError: true - }); + } as CallToolResult); } catch { // Task may have been cleaned up if test ended } diff --git a/test/server/elicitation.test.ts b/test/server/elicitation.test.ts index c6f297b46..a02fe5087 100644 --- a/test/server/elicitation.test.ts +++ b/test/server/elicitation.test.ts @@ -9,7 +9,7 @@ import { Client } from '../../src/client/index.js'; import { InMemoryTransport } from '../../src/inMemory.js'; -import { ElicitRequestFormParams, ElicitRequestSchema } from '../../src/types.js'; +import { ElicitRequestFormParams, ElicitRequestSchema, ElicitResult } from '../../src/types.js'; import { AjvJsonSchemaValidator } from '../../src/validation/ajv-provider.js'; import { CfWorkerJsonSchemaValidator } from '../../src/validation/cfworker-provider.js'; import { Server } from '../../src/server/index.js'; @@ -341,13 +341,13 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo client.setRequestHandler(ElicitRequestSchema, request => { requestCount++; if (request.params.message.includes('name')) { - return { action: 'accept', content: { name: 'Alice' } }; + return { action: 'accept' as const, content: { name: 'Alice' } } as ElicitResult; } else if (request.params.message.includes('age')) { - return { action: 'accept', content: { age: 30 } }; + return { action: 'accept' as const, content: { age: 30 } } as ElicitResult; } else if (request.params.message.includes('city')) { - return { action: 'accept', content: { city: 'New York' } }; + return { action: 'accept' as const, content: { city: 'New York' } } as ElicitResult; } - return { action: 'decline' }; + return { action: 'decline' as const } as ElicitResult; }); const nameResult = await server.elicitInput({ diff --git a/test/server/index.test.ts b/test/server/index.test.ts index e434e57fc..5a2f059e6 100644 --- a/test/server/index.test.ts +++ b/test/server/index.test.ts @@ -21,12 +21,15 @@ import { ResultSchema, SetLevelRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, - CreateTaskResultSchema + CreateTaskResultSchema, + type ElicitResult, + type CreateTaskResult, + type Result } from '../../src/types.js'; import { Server } from '../../src/server/index.js'; import { McpServer } from '../../src/server/mcp.js'; import { InMemoryTaskStore, InMemoryTaskMessageQueue } from '../../src/experimental/tasks/stores/in-memory.js'; -import { CallToolRequestSchema, CallToolResultSchema } from '../../src/types.js'; +import { CallToolRequestSchema, CallToolResultSchema, type CallToolResult } from '../../src/types.js'; import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator } from '../../src/validation/types.js'; import type { AnyObjectSchema } from '../../src/server/zod-compat.js'; import * as z3 from 'zod/v3'; @@ -453,9 +456,9 @@ test('should respect client elicitation capabilities', async () => { ); client.setRequestHandler(ElicitRequestSchema, params => ({ - action: 'accept', + action: 'accept' as const, content: { - username: params.params.message.includes('username') ? 'test-user' : undefined, + username: params.params.message.includes('username') ? 'test-user' : '', confirmed: true } })); @@ -491,7 +494,7 @@ test('should respect client elicitation capabilities', async () => { } }) ).resolves.toEqual({ - action: 'accept', + action: 'accept' as const, content: { username: 'test-user', confirmed: true @@ -537,9 +540,9 @@ test('should use elicitInput with mode: "form" by default for backwards compatib ); client.setRequestHandler(ElicitRequestSchema, params => ({ - action: 'accept', + action: 'accept' as const, content: { - username: params.params.message.includes('username') ? 'test-user' : undefined, + username: params.params.message.includes('username') ? 'test-user' : '', confirmed: true } })); @@ -574,7 +577,7 @@ test('should use elicitInput with mode: "form" by default for backwards compatib } }) ).resolves.toEqual({ - action: 'accept', + action: 'accept' as const, content: { username: 'test-user', confirmed: true @@ -711,7 +714,7 @@ test('should include form mode when sending elicitation form requests', async () client.setRequestHandler(ElicitRequestSchema, request => { receivedModes.push(request.params.mode ?? ''); return { - action: 'accept', + action: 'accept' as const, content: { confirmation: true } @@ -736,7 +739,7 @@ test('should include form mode when sending elicitation form requests', async () } }) ).resolves.toEqual({ - action: 'accept', + action: 'accept' as const, content: { confirmation: true } @@ -827,7 +830,7 @@ test('should reject elicitInput when client response violates requested schema', ); client.setRequestHandler(ElicitRequestSchema, () => ({ - action: 'accept', + action: 'accept' as const, // Bad response: missing required field `username` content: {} @@ -886,7 +889,7 @@ test('should wrap unexpected validator errors during elicitInput', async () => { ); client.setRequestHandler(ElicitRequestSchema, () => ({ - action: 'accept', + action: 'accept' as const, content: { username: 'ignored' } @@ -1149,7 +1152,7 @@ test('should validate elicitation response against requested schema', async () = // Set up client to return valid response client.setRequestHandler(ElicitRequestSchema, _request => ({ - action: 'accept', + action: 'accept' as const, content: { name: 'John Doe', email: 'john@example.com', @@ -1187,7 +1190,7 @@ test('should validate elicitation response against requested schema', async () = } }) ).resolves.toEqual({ - action: 'accept', + action: 'accept' as const, content: { name: 'John Doe', email: 'john@example.com', @@ -1227,7 +1230,7 @@ test('should reject elicitation response with invalid data', async () => { // Set up client to return invalid response (missing required field, invalid age) client.setRequestHandler(ElicitRequestSchema, _request => ({ - action: 'accept', + action: 'accept' as const, content: { email: '', // Invalid - too short age: -5 // Invalid age @@ -1951,9 +1954,11 @@ describe('createMessage backwards compatibility', () => { // Backwards compat: result.content should be single (not array) expect(result.model).toBe('test-model'); expect(Array.isArray(result.content)).toBe(false); - expect(result.content.type).toBe('text'); - if (result.content.type === 'text') { - expect(result.content.text).toBe('Hello from LLM'); + if (!Array.isArray(result.content)) { + expect(result.content.type).toBe('text'); + if (result.content.type === 'text') { + expect(result.content.text).toBe('Hello from LLM'); + } } }); @@ -2258,8 +2263,8 @@ describe('Task-based execution', () => { await new Promise(resolve => setTimeout(resolve, 10)); const result = { content: [{ type: 'text', text: 'Tool executed successfully!' }] - }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + } as CallToolResult; + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); })(); return { task }; @@ -2287,6 +2292,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - tools not in ClientCapabilities.tasks.requests in generated types, but needed for test tools: { call: {} } @@ -2377,6 +2383,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - tools not in ClientCapabilities.tasks.requests in generated types, but needed for test tools: { call: {} } @@ -2455,7 +2462,7 @@ describe('Task-based execution', () => { capturedElicitRequest = request; return { - action: 'accept', + action: 'accept' as const, content: { username: 'test-user' } @@ -2503,8 +2510,8 @@ describe('Task-based execution', () => { text: `Collected username: ${elicitResult.action === 'accept' && elicitResult.content ? (elicitResult.content as Record).username : 'none'}` } ] - }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + } as CallToolResult; + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); })(); return { task }; @@ -2594,9 +2601,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { const result = { - action: 'accept', + action: 'accept' as const, content: { username: 'server-test-user', confirmed: true } }; @@ -2605,7 +2612,7 @@ describe('Task-based execution', () => { const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2675,9 +2682,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { const result = { - action: 'accept', + action: 'accept' as const, content: { username: 'list-user' } }; @@ -2686,7 +2693,7 @@ describe('Task-based execution', () => { const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2754,9 +2761,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { const result = { - action: 'accept', + action: 'accept' as const, content: { username: 'result-user', confirmed: true } }; @@ -2765,7 +2772,7 @@ describe('Task-based execution', () => { const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2835,9 +2842,9 @@ describe('Task-based execution', () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { const result = { - action: 'accept', + action: 'accept' as const, content: { username: 'list-user' } }; @@ -2846,7 +2853,7 @@ describe('Task-based execution', () => { const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -2864,6 +2871,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - elicitation not in ServerCapabilities.tasks.requests in generated types, but needed for test elicitation: { create: {} } @@ -2959,8 +2967,8 @@ describe('Task-based execution', () => { await new Promise(resolve => setTimeout(resolve, delay)); const result = { content: [{ type: 'text', text: `Completed task ${taskNum || 'unknown'}` }] - }; - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + } as CallToolResult; + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); })(); return { task }; @@ -2988,6 +2996,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - tools not in ClientCapabilities.tasks.requests in generated types, but needed for test tools: { call: {} } @@ -3083,6 +3092,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - tools not in ClientCapabilities.tasks.requests in generated types, but needed for test tools: { call: {} } @@ -3122,7 +3132,7 @@ describe('Task-based execution', () => { ); client.setRequestHandler(ElicitRequestSchema, async () => ({ - action: 'accept', + action: 'accept' as const, content: { username: 'test' } })); @@ -3135,6 +3145,7 @@ describe('Task-based execution', () => { capabilities: { tasks: { requests: { + // @ts-expect-error - elicitation not in ServerCapabilities.tasks.requests in generated types, but needed for test elicitation: { create: {} } @@ -3178,9 +3189,9 @@ test('should respect client task capabilities', async () => { } ); - client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + client.setRequestHandler(ElicitRequestSchema, async (request, extra): Promise => { const result = { - action: 'accept', + action: 'accept' as const, content: { username: 'test-user' } }; @@ -3189,7 +3200,7 @@ test('should respect client task capabilities', async () => { const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl }); - await extra.taskStore.storeTaskResult(task.taskId, 'completed', result); + await extra.taskStore.storeTaskResult(task.taskId, 'completed', result as Result); // Return CreateTaskResult when task creation is requested return { task }; } @@ -3207,6 +3218,7 @@ test('should respect client task capabilities', async () => { capabilities: { tasks: { requests: { + // @ts-expect-error - elicitation not in ServerCapabilities.tasks.requests in generated types, but needed for test elicitation: { create: {} } diff --git a/test/server/mcp.test.ts b/test/server/mcp.test.ts index f6c2124e1..42baf83b8 100644 --- a/test/server/mcp.test.ts +++ b/test/server/mcp.test.ts @@ -8,6 +8,7 @@ import { CompleteResultSchema, ElicitRequestSchema, GetPromptResultSchema, + type JSONRPCNotification, ListPromptsResultSchema, ListResourcesResultSchema, ListResourceTemplatesResultSchema, @@ -68,7 +69,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { logging: {} } } ); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -262,7 +263,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -329,7 +330,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -394,7 +395,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -488,7 +489,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -577,7 +578,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -1875,11 +1876,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -1944,11 +1942,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -2099,7 +2094,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -2167,7 +2162,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -2239,7 +2234,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -2290,7 +2285,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -2343,7 +2338,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -3048,7 +3043,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -3125,7 +3120,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -3227,7 +3222,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -3284,7 +3279,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -5182,7 +5177,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { name: 'test server', version: '1.0' }); - const notifications: Notification[] = []; + const notifications: JSONRPCNotification[] = []; const client = new Client({ name: 'test client', version: '1.0' @@ -6259,11 +6254,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -6278,11 +6270,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } } } @@ -6311,7 +6300,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { setTimeout(async () => { await store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text' as const, text: `Processed: ${input}` }] - }); + } as CallToolResult); }, 200); return { task }; @@ -6364,11 +6353,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -6383,11 +6369,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } } } @@ -6416,7 +6399,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { setTimeout(async () => { await store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text' as const, text: `Result: ${value * 2}` }] - }); + } as CallToolResult); releaseLatch(); }, 150); @@ -6491,11 +6474,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } } } @@ -6524,7 +6504,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { setTimeout(async () => { await store.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text' as const, text: `Completed: ${data}` }] - }); + } as CallToolResult); releaseLatch(); }, 200); @@ -6592,11 +6572,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -6611,11 +6588,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } } } @@ -6642,7 +6616,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { await store.storeTaskResult(task.taskId, 'failed', { content: [{ type: 'text' as const, text: 'Error occurred' }], isError: true - }); + } as CallToolResult); releaseLatch(); }, 150); @@ -6698,11 +6672,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore @@ -6717,11 +6688,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { { capabilities: { tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } } } @@ -6799,11 +6767,8 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { capabilities: { tools: {}, tasks: { - requests: { - tools: { - call: {} - } - } + list: {}, + cancel: {} } }, taskStore diff --git a/test/server/stdio.test.ts b/test/server/stdio.test.ts index 86379c8a6..9b1279317 100644 --- a/test/server/stdio.test.ts +++ b/test/server/stdio.test.ts @@ -87,7 +87,9 @@ test('should read multiple messages', async () => { const finished = new Promise(resolve => { server.onmessage = message => { readMessages.push(message); - if (JSON.stringify(message) === JSON.stringify(messages[1])) { + // Compare message content instead of JSON.stringify (Zod reorders keys) + const msg1 = messages[1]; + if ('method' in message && 'method' in msg1 && message.method === msg1.method && message.jsonrpc === msg1.jsonrpc) { resolve(); } }; diff --git a/test/shared/protocol.test.ts b/test/shared/protocol.test.ts index 886dcbb21..d73aa4ff3 100644 --- a/test/shared/protocol.test.ts +++ b/test/shared/protocol.test.ts @@ -1,6 +1,7 @@ import { ZodType, z } from 'zod'; import { CallToolRequestSchema, + CallToolResult, ClientCapabilities, ErrorCode, JSONRPCMessage, @@ -157,7 +158,7 @@ describe('protocol tests', () => { test('should throw a timeout error if the request exceeds the timeout', async () => { await protocol.connect(transport); - const request = { method: 'example', params: {} }; + const request = { method: 'example', params: {} } as Request; try { const mockSchema: ZodType<{ result: string }> = z.object({ result: z.string() @@ -209,7 +210,7 @@ describe('protocol tests', () => { anotherField: 123 } } - }; + } as Request; const mockSchema: ZodType<{ result: string }> = z.object({ result: z.string() }); @@ -290,7 +291,7 @@ describe('protocol tests', () => { customField: 'customValue' } } - }; + } as Request; const mockSchema: ZodType<{ result: string }> = z.object({ result: z.string() }); @@ -1059,7 +1060,7 @@ describe('Task-based execution', () => { customField: 'customValue' } } - }; + } as Request; const resultSchema = z.object({ content: z.array(z.object({ type: z.literal('text'), text: z.string() })) @@ -1257,13 +1258,13 @@ describe('Task-based execution', () => { await protocol.connect(transport); - protocol.setRequestHandler(CallToolRequestSchema, async request => { + protocol.setRequestHandler(CallToolRequestSchema, async (request, extra) => { // Tool implementor can access task creation parameters from request.params.task expect(request.params.task).toEqual({ ttl: 60000, pollInterval: 1000 }); - return { result: 'success' }; + return { result: 'success' } as Result; }); transport.onmessage?.({ @@ -1299,7 +1300,7 @@ describe('Task-based execution', () => { { method: 'test/method', params: {} - } + } as unknown as Request ); // Manually set status to completed for this test await mockTaskStore.updateTaskStatus(task1.taskId, 'completed'); @@ -1313,7 +1314,7 @@ describe('Task-based execution', () => { { method: 'test/method', params: {} - } + } as unknown as Request ); protocol = new (class extends Protocol { @@ -1374,7 +1375,7 @@ describe('Task-based execution', () => { { method: 'test/method', params: {} - } + } as unknown as Request ); protocol = new (class extends Protocol { @@ -1579,7 +1580,7 @@ describe('Task-based execution', () => { const task = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); mockTaskStore.getTask.mockResolvedValue(task); mockTaskStore.updateTaskStatus.mockImplementation(async (taskId: string, status: string) => { @@ -1671,7 +1672,7 @@ describe('Task-based execution', () => { const completedTask = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); // Set task to completed status await mockTaskStore.updateTaskStatus(completedTask.taskId, 'completed'); completedTask.status = 'completed'; @@ -1747,8 +1748,8 @@ describe('Task-based execution', () => { expect.any(Object) ); expect(result._meta).toBeDefined(); - expect(result.taskId).toBe('task-to-delete'); - expect(result.status).toBe('cancelled'); + expect((result as any).taskId).toBe('task-to-delete'); + expect((result as any).status).toBe('cancelled'); }); }); @@ -1760,7 +1761,7 @@ describe('Task-based execution', () => { const task = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); const serverProtocol = new (class extends Protocol { protected assertCapabilityForMethod(): void {} @@ -1810,7 +1811,7 @@ describe('Task-based execution', () => { const task = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); const serverProtocol = new (class extends Protocol { protected assertCapabilityForMethod(): void {} @@ -1859,7 +1860,7 @@ describe('Task-based execution', () => { await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); const serverProtocol = new (class extends Protocol { protected assertCapabilityForMethod(): void {} @@ -1896,7 +1897,7 @@ describe('Task-based execution', () => { const task = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); const serverProtocol = new (class extends Protocol { protected assertCapabilityForMethod(): void {} @@ -1935,11 +1936,11 @@ describe('Task-based execution', () => { const task = await mockTaskStore.createTask({}, 1, { method: 'test/method', params: {} - }); + } as unknown as Request); const testResult = { content: [{ type: 'text', text: 'test result' }] - }; + } as Result; await mockTaskStore.storeTaskResult(task.taskId, 'completed', testResult); @@ -1972,7 +1973,7 @@ describe('Task-based execution', () => { expect(sendSpy).toHaveBeenCalledWith( expect.objectContaining({ result: expect.objectContaining({ - content: testResult.content, + content: (testResult as any).content, _meta: expect.objectContaining({ [RELATED_TASK_META_KEY]: { taskId: task.taskId @@ -2000,16 +2001,16 @@ describe('Task-based execution', () => { await serverProtocol.connect(serverTransport); // Set up a handler that uses sendRequest and sendNotification - serverProtocol.setRequestHandler(CallToolRequestSchema, async (_request, extra) => { + serverProtocol.setRequestHandler(CallToolRequestSchema, async (request, extra) => { // Send a notification using the extra.sendNotification await extra.sendNotification({ method: 'notifications/message', params: { level: 'info', data: 'test' } - }); + } as Notification); return { content: [{ type: 'text', text: 'done' }] - }; + } as Result; }); // Send a request with related-task metadata @@ -2086,7 +2087,7 @@ describe('Request Cancellation vs Task Cancellation', () => { let wasAborted = false; const TestRequestSchema = z.object({ method: z.literal('test/longRunning'), - params: z.optional(z.record(z.unknown())) + params: z.record(z.string(), z.unknown()).optional() }); protocol.setRequestHandler(TestRequestSchema, async (_request, extra) => { // Simulate a long-running operation @@ -2135,7 +2136,7 @@ describe('Request Cancellation vs Task Cancellation', () => { const task = await taskStore.createTask({ ttl: 60000 }, 'req-1', { method: 'test/method', params: {} - }); + } as unknown as Request); // Send cancellation notification for the request if (transport.onmessage) { @@ -2167,7 +2168,7 @@ describe('Request Cancellation vs Task Cancellation', () => { const task = await taskStore.createTask({ ttl: 60000 }, 'req-1', { method: 'test/method', params: {} - }); + } as unknown as Request); // Cancel the task using tasks/cancel if (transport.onmessage) { @@ -2201,7 +2202,7 @@ describe('Request Cancellation vs Task Cancellation', () => { const task = await taskStore.createTask({ ttl: 60000 }, 'req-1', { method: 'test/method', params: {} - }); + } as unknown as Request); await taskStore.updateTaskStatus(task.taskId, 'completed'); // Try to cancel the completed task @@ -2273,7 +2274,7 @@ describe('Request Cancellation vs Task Cancellation', () => { const task = await taskStore.createTask({ ttl: 60000 }, 'req-1', { method: 'test/method', params: {} - }); + } as unknown as Request); // Cancel the request (not the task) if (transport.onmessage) { @@ -2301,7 +2302,7 @@ describe('Request Cancellation vs Task Cancellation', () => { let requestCompleted = false; const TestMethodSchema = z.object({ method: z.literal('test/method'), - params: z.optional(z.record(z.unknown())) + params: z.record(z.string(), z.unknown()).optional() }); protocol.setRequestHandler(TestMethodSchema, async () => { await new Promise(resolve => setTimeout(resolve, 50)); @@ -2313,7 +2314,7 @@ describe('Request Cancellation vs Task Cancellation', () => { const task = await taskStore.createTask({ ttl: 60000 }, 'req-1', { method: 'test/method', params: {} - }); + } as unknown as Request); // Start a request if (transport.onmessage) { @@ -2322,7 +2323,7 @@ describe('Request Cancellation vs Task Cancellation', () => { id: 123, method: 'test/method', params: {} - }); + } as unknown as Request); } // Cancel the task (not the request) @@ -2387,8 +2388,8 @@ describe('Progress notification support for tasks', () => { const progressCallback = vi.fn(); const request = { - method: 'tools/call', - params: { name: 'test-tool' } + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; const resultSchema = z.object({ @@ -2487,18 +2488,18 @@ describe('Progress notification support for tasks', () => { setTimeout(async () => { await extra.taskStore!.storeTaskResult(task.taskId, 'completed', { content: [{ type: 'text', text: 'Done' }] - }); + } as Result); }, 50); - return { task }; + return { task } as Result; } - return { content: [] }; + return { content: [] as any } as Result; }); const progressCallback = vi.fn(); const request = { - method: 'tools/call', - params: { name: 'test-tool' } + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; const resultSchema = z.object({ @@ -2569,9 +2570,9 @@ describe('Progress notification support for tasks', () => { // Simulate task completion by calling through the protocol's task store // This will trigger the cleanup logic - const mockRequest = { jsonrpc: '2.0' as const, id: 999, method: 'test', params: {} }; + const mockRequest = { jsonrpc: '2.0' as const, id: 999, method: 'test', params: {} } as unknown as Request; const requestTaskStore = (protocol as unknown as TestProtocol).requestTaskStore(mockRequest, undefined); - await requestTaskStore.storeTaskResult(taskId, 'completed', { content: [] }); + await requestTaskStore.storeTaskResult(taskId, 'completed', { content: [] } as Result); // Wait for all async operations including notification sending to complete await new Promise(resolve => setTimeout(resolve, 50)); @@ -2615,8 +2616,8 @@ describe('Progress notification support for tasks', () => { const progressCallback = vi.fn(); const request = { - method: 'tools/call', - params: { name: 'test-tool' } + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; const resultSchema = z.object({ @@ -2660,7 +2661,7 @@ describe('Progress notification support for tasks', () => { await taskStore.storeTaskResult(taskId, 'failed', { content: [], isError: true - }); + } as CallToolResult); // Manually trigger the status notification if (transport.onmessage) { @@ -2713,8 +2714,8 @@ describe('Progress notification support for tasks', () => { const progressCallback = vi.fn(); const request = { - method: 'tools/call', - params: { name: 'test-tool' } + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; const resultSchema = z.object({ @@ -2808,8 +2809,8 @@ describe('Progress notification support for tasks', () => { const progressCallback = vi.fn(); const request = { - method: 'tools/call', - params: { name: 'test-tool' } + method: 'tools/call' as const, + params: { name: 'test-tool', arguments: {} } }; const resultSchema = z.object({ @@ -3074,7 +3075,10 @@ describe('Message interception for task-related notifications', () => { await server.connect(transport); // Create a task first - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send a notification with related task metadata await server.notification( @@ -3138,7 +3142,10 @@ describe('Message interception for task-related notifications', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Fill the queue to max capacity (100 messages) for (let i = 0; i < 100; i++) { @@ -3218,7 +3225,10 @@ describe('Message interception for task-related notifications', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send multiple notifications for (let i = 0; i < 5; i++) { @@ -3260,7 +3270,10 @@ describe('Message interception for task-related requests', () => { await server.connect(transport); // Create a task first - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send a request with related task metadata (don't await - we're testing queuing) const requestPromise = server.request( @@ -3351,7 +3364,10 @@ describe('Message interception for task-related requests', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send a request with related task metadata const requestPromise = server.request( @@ -3404,7 +3420,10 @@ describe('Message interception for task-related requests', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send a request with related task metadata const requestPromise = server.request( @@ -3428,7 +3447,7 @@ describe('Message interception for task-related requests', () => { message: { jsonrpc: '2.0', id: requestId, - result: { message: 'pong' } + result: { message: 'pong' } as Result }, timestamp: Date.now() }); @@ -3474,7 +3493,10 @@ describe('Message interception for task-related requests', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Send a request with related task metadata void server.request( @@ -3502,7 +3524,7 @@ describe('Message interception for task-related requests', () => { message: { jsonrpc: '2.0', id: requestId, - result: { message: 'pong' } + result: { message: 'pong' } as Result }, timestamp: Date.now() }); @@ -3545,7 +3567,10 @@ describe('Message interception for task-related requests', () => { await server.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { + method: 'tools/call', + params: { name: 'test-tool', arguments: {} } + } as unknown as Request); // Fill the queue to max capacity (100 messages) const promises: Promise[] = []; @@ -3681,13 +3706,13 @@ describe('Message Interception', () => { method: z.literal('test/taskRequest'), params: z .object({ - _meta: z.optional(z.record(z.unknown())) + _meta: z.optional(z.record(z.string(), z.unknown())) }) .passthrough() }); protocol.setRequestHandler(TestRequestSchema, async () => { - return { content: 'test result' } as Result; + return { content: 'test result' } as Result as Result; }); // Simulate an incoming request with relatedTask metadata @@ -3728,7 +3753,7 @@ describe('Message Interception', () => { method: z.literal('test/taskRequestError'), params: z .object({ - _meta: z.optional(z.record(z.unknown())) + _meta: z.optional(z.record(z.string(), z.unknown())) }) .passthrough() }); @@ -3808,11 +3833,11 @@ describe('Message Interception', () => { // Set up a request handler const TestRequestSchema = z.object({ method: z.literal('test/normalRequest'), - params: z.optional(z.record(z.unknown())) + params: z.optional(z.record(z.string(), z.unknown())) }); protocol.setRequestHandler(TestRequestSchema, async () => { - return { content: 'normal result' } as Result; + return { content: 'normal result' } as Result as Result; }); // Simulate an incoming request WITHOUT relatedTask metadata @@ -4150,7 +4175,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue some messages for the task @@ -4179,7 +4204,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue a message @@ -4215,7 +4240,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue some messages @@ -4252,7 +4277,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue a request (catch rejection to avoid unhandled promise rejection) @@ -4296,7 +4321,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue some messages @@ -4325,7 +4350,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue a request (catch the rejection to avoid unhandled promise rejection) @@ -4358,7 +4383,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue multiple requests (catch rejections to avoid unhandled promise rejections) @@ -4408,7 +4433,7 @@ describe('Queue lifecycle management', () => { await protocol.connect(transport); // Create a task - const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} }); + const task = await mockTaskStore.createTask({}, 1, { method: 'test', params: {} } as unknown as Request); const taskId = task.taskId; // Queue a request (catch rejection to avoid unhandled promise rejection) @@ -4908,7 +4933,7 @@ describe('Error handling for missing resolvers', () => { await protocol.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); // Enqueue a response message without a corresponding resolver await taskMessageQueue.enqueue(task.taskId, { @@ -4916,7 +4941,7 @@ describe('Error handling for missing resolvers', () => { message: { jsonrpc: '2.0', id: 999, // Non-existent request ID - result: { content: [] } + result: {} as Result }, timestamp: Date.now() }); @@ -4953,7 +4978,7 @@ describe('Error handling for missing resolvers', () => { await protocol.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); // Enqueue a response with missing resolver, then a valid notification await taskMessageQueue.enqueue(task.taskId, { @@ -4961,7 +4986,7 @@ describe('Error handling for missing resolvers', () => { message: { jsonrpc: '2.0', id: 999, - result: { content: [] } + result: { content: [] } as Result }, timestamp: Date.now() }); @@ -4994,7 +5019,7 @@ describe('Error handling for missing resolvers', () => { await protocol.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); // Enqueue a request without storing a resolver await taskMessageQueue.enqueue(task.taskId, { @@ -5024,7 +5049,7 @@ describe('Error handling for missing resolvers', () => { await protocol.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const requestId = 42; const resolverMock = vi.fn(); @@ -5064,7 +5089,7 @@ describe('Error handling for missing resolvers', () => { await protocol.connect(transport); // Create a task - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const testProtocol = protocol as unknown as TestProtocol; @@ -5150,7 +5175,7 @@ describe('Error handling for missing resolvers', () => { const mockResponse: JSONRPCResultResponse = { jsonrpc: '2.0', id: messageId, - result: { content: [] } + result: { content: [] } as Result }; responseResolver(mockResponse); @@ -5168,14 +5193,14 @@ describe('Error handling for missing resolvers', () => { it('should not throw when processing response with missing resolver', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); await taskMessageQueue.enqueue(task.taskId, { type: 'response', message: { jsonrpc: '2.0', id: 999, - result: { content: [] } + result: { content: [] } as Result }, timestamp: Date.now() }); @@ -5200,7 +5225,7 @@ describe('Error handling for missing resolvers', () => { it('should not throw during task cleanup with missing resolvers', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); await taskMessageQueue.enqueue(task.taskId, { type: 'request', @@ -5224,7 +5249,7 @@ describe('Error handling for missing resolvers', () => { it('should route error messages to resolvers correctly', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const requestId = 42; const resolverMock = vi.fn(); @@ -5277,7 +5302,7 @@ describe('Error handling for missing resolvers', () => { it('should log error for unknown request ID in error messages', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); // Enqueue an error message without a corresponding resolver await taskMessageQueue.enqueue(task.taskId, { @@ -5321,7 +5346,7 @@ describe('Error handling for missing resolvers', () => { it('should handle error messages with data field', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const requestId = 42; const resolverMock = vi.fn(); @@ -5370,7 +5395,7 @@ describe('Error handling for missing resolvers', () => { it('should not throw when processing error with missing resolver', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); await taskMessageQueue.enqueue(task.taskId, { type: 'error', @@ -5407,7 +5432,7 @@ describe('Error handling for missing resolvers', () => { it('should handle mixed response and error messages in queue', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const testProtocol = protocol as unknown as TestProtocol; // Set up resolvers for multiple requests @@ -5425,7 +5450,7 @@ describe('Error handling for missing resolvers', () => { message: { jsonrpc: '2.0', id: 1, - result: { content: [{ type: 'text', text: 'Success' }] } + result: { content: [{ type: 'text', text: 'Success' }] } as Result }, timestamp: Date.now() }); @@ -5448,7 +5473,7 @@ describe('Error handling for missing resolvers', () => { message: { jsonrpc: '2.0', id: 3, - result: { content: [{ type: 'text', text: 'Another success' }] } + result: { content: [{ type: 'text', text: 'Another success' }] } as Result }, timestamp: Date.now() }); @@ -5493,7 +5518,7 @@ describe('Error handling for missing resolvers', () => { it('should maintain FIFO order when processing responses and errors', async () => { await protocol.connect(transport); - const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} }); + const task = await taskStore.createTask({ ttl: 60000 }, 1, { method: 'test', params: {} } as unknown as Request); const testProtocol = protocol as unknown as TestProtocol; const callOrder: number[] = []; diff --git a/test/spec.types.test.ts b/test/spec.types.test.ts deleted file mode 100644 index 1fff0f0ff..000000000 --- a/test/spec.types.test.ts +++ /dev/null @@ -1,740 +0,0 @@ -/** - * This contains: - * - Static type checks to verify the Spec's types are compatible with the SDK's types - * (mutually assignable, w/ slight affordances to get rid of ZodObject.passthrough() index signatures, etc) - * - Runtime checks to verify each Spec type has a static check - * (note: a few don't have SDK types, see MISSING_SDK_TYPES below) - */ -import * as SDKTypes from '../src/types.js'; -import * as SpecTypes from '../src/spec.types.js'; -import fs from 'node:fs'; - -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ - -// Adds the `jsonrpc` property to a type, to match the on-wire format of notifications. -type WithJSONRPC = T & { jsonrpc: '2.0' }; - -// Adds the `jsonrpc` and `id` properties to a type, to match the on-wire format of requests. -type WithJSONRPCRequest = T & { jsonrpc: '2.0'; id: SDKTypes.RequestId }; - -type IsUnknown = [unknown] extends [T] ? ([T] extends [unknown] ? true : false) : false; - -// Turns {x?: unknown} into {x: unknown} but keeps {_meta?: unknown} unchanged (and leaves other optional properties unchanged, e.g. {x?: string}). -// This works around an apparent quirk of ZodObject.unknown() (makes fields optional) -type MakeUnknownsNotOptional = - IsUnknown extends true - ? unknown - : T extends object - ? T extends Array - ? Array> - : T extends Function - ? T - : Pick & { - // Start with empty object to avoid duplicates - // Make unknown properties required (except _meta) - [K in keyof T as '_meta' extends K ? never : IsUnknown extends true ? K : never]-?: unknown; - } & Pick< - T, - { - // Pick all _meta and non-unknown properties with original modifiers - [K in keyof T]: '_meta' extends K ? K : IsUnknown extends true ? never : K; - }[keyof T] - > & { - // Recurse on the picked properties - [K in keyof Pick< - T, - { - [K in keyof T]: '_meta' extends K ? K : IsUnknown extends true ? never : K; - }[keyof T] - >]: MakeUnknownsNotOptional; - } - : T; - -// Targeted fix: in spec, treat ClientCapabilities.elicitation?: object as Record -type FixSpecClientCapabilities = T extends { elicitation?: object } - ? Omit & { elicitation?: Record } - : T; - -// Targeted fix: in spec, ServerCapabilities needs index signature to match SDK's passthrough -type FixSpecServerCapabilities = T & { [x: string]: unknown }; - -type FixSpecInitializeResult = T extends { capabilities: infer C } ? T & { capabilities: FixSpecServerCapabilities } : T; - -type FixSpecInitializeRequestParams = T extends { capabilities: infer C } - ? Omit & { capabilities: FixSpecClientCapabilities } - : T; - -type FixSpecInitializeRequest = T extends { params: infer P } ? Omit & { params: FixSpecInitializeRequestParams

} : T; - -type FixSpecClientRequest = T extends { params: infer P } ? Omit & { params: FixSpecInitializeRequestParams

} : T; - -// Targeted fix: CreateMessageResult in SDK uses single content for v1.x backwards compat. -// The full array-capable type is CreateMessageResultWithTools. -// This will be aligned with schema in v2.0. -// Narrows content from SamplingMessageContentBlock (includes tool types) to basic content types only. -type NarrowToBasicContent = C extends { type: 'text' | 'image' | 'audio' } ? C : never; -type FixSpecCreateMessageResult = T extends { content: infer C; role: infer R; model: infer M } - ? { - _meta?: { [key: string]: unknown }; - model: M; - role: R; - stopReason?: string; - content: C extends (infer U)[] ? NarrowToBasicContent : NarrowToBasicContent; - } - : T; - -const sdkTypeChecks = { - RequestParams: (sdk: SDKTypes.RequestParams, spec: SpecTypes.RequestParams) => { - sdk = spec; - spec = sdk; - }, - NotificationParams: (sdk: SDKTypes.NotificationParams, spec: SpecTypes.NotificationParams) => { - sdk = spec; - spec = sdk; - }, - CancelledNotificationParams: (sdk: SDKTypes.CancelledNotificationParams, spec: SpecTypes.CancelledNotificationParams) => { - sdk = spec; - spec = sdk; - }, - InitializeRequestParams: ( - sdk: SDKTypes.InitializeRequestParams, - spec: FixSpecInitializeRequestParams - ) => { - sdk = spec; - spec = sdk; - }, - ProgressNotificationParams: (sdk: SDKTypes.ProgressNotificationParams, spec: SpecTypes.ProgressNotificationParams) => { - sdk = spec; - spec = sdk; - }, - ResourceRequestParams: (sdk: SDKTypes.ResourceRequestParams, spec: SpecTypes.ResourceRequestParams) => { - sdk = spec; - spec = sdk; - }, - ReadResourceRequestParams: (sdk: SDKTypes.ReadResourceRequestParams, spec: SpecTypes.ReadResourceRequestParams) => { - sdk = spec; - spec = sdk; - }, - SubscribeRequestParams: (sdk: SDKTypes.SubscribeRequestParams, spec: SpecTypes.SubscribeRequestParams) => { - sdk = spec; - spec = sdk; - }, - UnsubscribeRequestParams: (sdk: SDKTypes.UnsubscribeRequestParams, spec: SpecTypes.UnsubscribeRequestParams) => { - sdk = spec; - spec = sdk; - }, - ResourceUpdatedNotificationParams: ( - sdk: SDKTypes.ResourceUpdatedNotificationParams, - spec: SpecTypes.ResourceUpdatedNotificationParams - ) => { - sdk = spec; - spec = sdk; - }, - GetPromptRequestParams: (sdk: SDKTypes.GetPromptRequestParams, spec: SpecTypes.GetPromptRequestParams) => { - sdk = spec; - spec = sdk; - }, - CallToolRequestParams: (sdk: SDKTypes.CallToolRequestParams, spec: SpecTypes.CallToolRequestParams) => { - sdk = spec; - spec = sdk; - }, - SetLevelRequestParams: (sdk: SDKTypes.SetLevelRequestParams, spec: SpecTypes.SetLevelRequestParams) => { - sdk = spec; - spec = sdk; - }, - LoggingMessageNotificationParams: ( - sdk: MakeUnknownsNotOptional, - spec: SpecTypes.LoggingMessageNotificationParams - ) => { - sdk = spec; - spec = sdk; - }, - CreateMessageRequestParams: (sdk: SDKTypes.CreateMessageRequestParams, spec: SpecTypes.CreateMessageRequestParams) => { - sdk = spec; - spec = sdk; - }, - CompleteRequestParams: (sdk: SDKTypes.CompleteRequestParams, spec: SpecTypes.CompleteRequestParams) => { - sdk = spec; - spec = sdk; - }, - ElicitRequestParams: (sdk: SDKTypes.ElicitRequestParams, spec: SpecTypes.ElicitRequestParams) => { - sdk = spec; - spec = sdk; - }, - ElicitRequestFormParams: (sdk: SDKTypes.ElicitRequestFormParams, spec: SpecTypes.ElicitRequestFormParams) => { - sdk = spec; - spec = sdk; - }, - ElicitRequestURLParams: (sdk: SDKTypes.ElicitRequestURLParams, spec: SpecTypes.ElicitRequestURLParams) => { - sdk = spec; - spec = sdk; - }, - ElicitationCompleteNotification: ( - sdk: WithJSONRPC, - spec: SpecTypes.ElicitationCompleteNotification - ) => { - sdk = spec; - spec = sdk; - }, - PaginatedRequestParams: (sdk: SDKTypes.PaginatedRequestParams, spec: SpecTypes.PaginatedRequestParams) => { - sdk = spec; - spec = sdk; - }, - CancelledNotification: (sdk: WithJSONRPC, spec: SpecTypes.CancelledNotification) => { - sdk = spec; - spec = sdk; - }, - BaseMetadata: (sdk: SDKTypes.BaseMetadata, spec: SpecTypes.BaseMetadata) => { - sdk = spec; - spec = sdk; - }, - Implementation: (sdk: SDKTypes.Implementation, spec: SpecTypes.Implementation) => { - sdk = spec; - spec = sdk; - }, - ProgressNotification: (sdk: WithJSONRPC, spec: SpecTypes.ProgressNotification) => { - sdk = spec; - spec = sdk; - }, - SubscribeRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.SubscribeRequest) => { - sdk = spec; - spec = sdk; - }, - UnsubscribeRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.UnsubscribeRequest) => { - sdk = spec; - spec = sdk; - }, - PaginatedRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.PaginatedRequest) => { - sdk = spec; - spec = sdk; - }, - PaginatedResult: (sdk: SDKTypes.PaginatedResult, spec: SpecTypes.PaginatedResult) => { - sdk = spec; - spec = sdk; - }, - ListRootsRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ListRootsRequest) => { - sdk = spec; - spec = sdk; - }, - ListRootsResult: (sdk: SDKTypes.ListRootsResult, spec: SpecTypes.ListRootsResult) => { - sdk = spec; - spec = sdk; - }, - Root: (sdk: SDKTypes.Root, spec: SpecTypes.Root) => { - sdk = spec; - spec = sdk; - }, - ElicitRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ElicitRequest) => { - sdk = spec; - spec = sdk; - }, - ElicitResult: (sdk: SDKTypes.ElicitResult, spec: SpecTypes.ElicitResult) => { - sdk = spec; - spec = sdk; - }, - CompleteRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.CompleteRequest) => { - sdk = spec; - spec = sdk; - }, - CompleteResult: (sdk: SDKTypes.CompleteResult, spec: SpecTypes.CompleteResult) => { - sdk = spec; - spec = sdk; - }, - ProgressToken: (sdk: SDKTypes.ProgressToken, spec: SpecTypes.ProgressToken) => { - sdk = spec; - spec = sdk; - }, - Cursor: (sdk: SDKTypes.Cursor, spec: SpecTypes.Cursor) => { - sdk = spec; - spec = sdk; - }, - Request: (sdk: SDKTypes.Request, spec: SpecTypes.Request) => { - sdk = spec; - spec = sdk; - }, - Result: (sdk: SDKTypes.Result, spec: SpecTypes.Result) => { - sdk = spec; - spec = sdk; - }, - RequestId: (sdk: SDKTypes.RequestId, spec: SpecTypes.RequestId) => { - sdk = spec; - spec = sdk; - }, - JSONRPCRequest: (sdk: SDKTypes.JSONRPCRequest, spec: SpecTypes.JSONRPCRequest) => { - sdk = spec; - spec = sdk; - }, - JSONRPCNotification: (sdk: SDKTypes.JSONRPCNotification, spec: SpecTypes.JSONRPCNotification) => { - sdk = spec; - spec = sdk; - }, - JSONRPCResponse: (sdk: SDKTypes.JSONRPCResponse, spec: SpecTypes.JSONRPCResponse) => { - sdk = spec; - spec = sdk; - }, - EmptyResult: (sdk: SDKTypes.EmptyResult, spec: SpecTypes.EmptyResult) => { - sdk = spec; - spec = sdk; - }, - Notification: (sdk: SDKTypes.Notification, spec: SpecTypes.Notification) => { - sdk = spec; - spec = sdk; - }, - ClientResult: (sdk: SDKTypes.ClientResult, spec: SpecTypes.ClientResult) => { - sdk = spec; - spec = sdk; - }, - ClientNotification: (sdk: WithJSONRPC, spec: SpecTypes.ClientNotification) => { - sdk = spec; - spec = sdk; - }, - ServerResult: (sdk: SDKTypes.ServerResult, spec: SpecTypes.ServerResult) => { - sdk = spec; - spec = sdk; - }, - ResourceTemplateReference: (sdk: SDKTypes.ResourceTemplateReference, spec: SpecTypes.ResourceTemplateReference) => { - sdk = spec; - spec = sdk; - }, - PromptReference: (sdk: SDKTypes.PromptReference, spec: SpecTypes.PromptReference) => { - sdk = spec; - spec = sdk; - }, - ToolAnnotations: (sdk: SDKTypes.ToolAnnotations, spec: SpecTypes.ToolAnnotations) => { - sdk = spec; - spec = sdk; - }, - Tool: (sdk: SDKTypes.Tool, spec: SpecTypes.Tool) => { - sdk = spec; - spec = sdk; - }, - ListToolsRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ListToolsRequest) => { - sdk = spec; - spec = sdk; - }, - ListToolsResult: (sdk: SDKTypes.ListToolsResult, spec: SpecTypes.ListToolsResult) => { - sdk = spec; - spec = sdk; - }, - CallToolResult: (sdk: SDKTypes.CallToolResult, spec: SpecTypes.CallToolResult) => { - sdk = spec; - spec = sdk; - }, - CallToolRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.CallToolRequest) => { - sdk = spec; - spec = sdk; - }, - ToolListChangedNotification: (sdk: WithJSONRPC, spec: SpecTypes.ToolListChangedNotification) => { - sdk = spec; - spec = sdk; - }, - ResourceListChangedNotification: ( - sdk: WithJSONRPC, - spec: SpecTypes.ResourceListChangedNotification - ) => { - sdk = spec; - spec = sdk; - }, - PromptListChangedNotification: ( - sdk: WithJSONRPC, - spec: SpecTypes.PromptListChangedNotification - ) => { - sdk = spec; - spec = sdk; - }, - RootsListChangedNotification: ( - sdk: WithJSONRPC, - spec: SpecTypes.RootsListChangedNotification - ) => { - sdk = spec; - spec = sdk; - }, - ResourceUpdatedNotification: (sdk: WithJSONRPC, spec: SpecTypes.ResourceUpdatedNotification) => { - sdk = spec; - spec = sdk; - }, - SamplingMessage: (sdk: SDKTypes.SamplingMessage, spec: SpecTypes.SamplingMessage) => { - sdk = spec; - spec = sdk; - }, - CreateMessageResult: (sdk: SDKTypes.CreateMessageResult, spec: FixSpecCreateMessageResult) => { - sdk = spec; - spec = sdk; - }, - SetLevelRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.SetLevelRequest) => { - sdk = spec; - spec = sdk; - }, - PingRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.PingRequest) => { - sdk = spec; - spec = sdk; - }, - InitializedNotification: (sdk: WithJSONRPC, spec: SpecTypes.InitializedNotification) => { - sdk = spec; - spec = sdk; - }, - ListResourcesRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ListResourcesRequest) => { - sdk = spec; - spec = sdk; - }, - ListResourcesResult: (sdk: SDKTypes.ListResourcesResult, spec: SpecTypes.ListResourcesResult) => { - sdk = spec; - spec = sdk; - }, - ListResourceTemplatesRequest: ( - sdk: WithJSONRPCRequest, - spec: SpecTypes.ListResourceTemplatesRequest - ) => { - sdk = spec; - spec = sdk; - }, - ListResourceTemplatesResult: (sdk: SDKTypes.ListResourceTemplatesResult, spec: SpecTypes.ListResourceTemplatesResult) => { - sdk = spec; - spec = sdk; - }, - ReadResourceRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ReadResourceRequest) => { - sdk = spec; - spec = sdk; - }, - ReadResourceResult: (sdk: SDKTypes.ReadResourceResult, spec: SpecTypes.ReadResourceResult) => { - sdk = spec; - spec = sdk; - }, - ResourceContents: (sdk: SDKTypes.ResourceContents, spec: SpecTypes.ResourceContents) => { - sdk = spec; - spec = sdk; - }, - TextResourceContents: (sdk: SDKTypes.TextResourceContents, spec: SpecTypes.TextResourceContents) => { - sdk = spec; - spec = sdk; - }, - BlobResourceContents: (sdk: SDKTypes.BlobResourceContents, spec: SpecTypes.BlobResourceContents) => { - sdk = spec; - spec = sdk; - }, - Resource: (sdk: SDKTypes.Resource, spec: SpecTypes.Resource) => { - sdk = spec; - spec = sdk; - }, - ResourceTemplate: (sdk: SDKTypes.ResourceTemplate, spec: SpecTypes.ResourceTemplate) => { - sdk = spec; - spec = sdk; - }, - PromptArgument: (sdk: SDKTypes.PromptArgument, spec: SpecTypes.PromptArgument) => { - sdk = spec; - spec = sdk; - }, - Prompt: (sdk: SDKTypes.Prompt, spec: SpecTypes.Prompt) => { - sdk = spec; - spec = sdk; - }, - ListPromptsRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ListPromptsRequest) => { - sdk = spec; - spec = sdk; - }, - ListPromptsResult: (sdk: SDKTypes.ListPromptsResult, spec: SpecTypes.ListPromptsResult) => { - sdk = spec; - spec = sdk; - }, - GetPromptRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.GetPromptRequest) => { - sdk = spec; - spec = sdk; - }, - TextContent: (sdk: SDKTypes.TextContent, spec: SpecTypes.TextContent) => { - sdk = spec; - spec = sdk; - }, - ImageContent: (sdk: SDKTypes.ImageContent, spec: SpecTypes.ImageContent) => { - sdk = spec; - spec = sdk; - }, - AudioContent: (sdk: SDKTypes.AudioContent, spec: SpecTypes.AudioContent) => { - sdk = spec; - spec = sdk; - }, - EmbeddedResource: (sdk: SDKTypes.EmbeddedResource, spec: SpecTypes.EmbeddedResource) => { - sdk = spec; - spec = sdk; - }, - ResourceLink: (sdk: SDKTypes.ResourceLink, spec: SpecTypes.ResourceLink) => { - sdk = spec; - spec = sdk; - }, - ContentBlock: (sdk: SDKTypes.ContentBlock, spec: SpecTypes.ContentBlock) => { - sdk = spec; - spec = sdk; - }, - PromptMessage: (sdk: SDKTypes.PromptMessage, spec: SpecTypes.PromptMessage) => { - sdk = spec; - spec = sdk; - }, - GetPromptResult: (sdk: SDKTypes.GetPromptResult, spec: SpecTypes.GetPromptResult) => { - sdk = spec; - spec = sdk; - }, - BooleanSchema: (sdk: SDKTypes.BooleanSchema, spec: SpecTypes.BooleanSchema) => { - sdk = spec; - spec = sdk; - }, - StringSchema: (sdk: SDKTypes.StringSchema, spec: SpecTypes.StringSchema) => { - sdk = spec; - spec = sdk; - }, - NumberSchema: (sdk: SDKTypes.NumberSchema, spec: SpecTypes.NumberSchema) => { - sdk = spec; - spec = sdk; - }, - EnumSchema: (sdk: SDKTypes.EnumSchema, spec: SpecTypes.EnumSchema) => { - sdk = spec; - spec = sdk; - }, - UntitledSingleSelectEnumSchema: (sdk: SDKTypes.UntitledSingleSelectEnumSchema, spec: SpecTypes.UntitledSingleSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - TitledSingleSelectEnumSchema: (sdk: SDKTypes.TitledSingleSelectEnumSchema, spec: SpecTypes.TitledSingleSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - SingleSelectEnumSchema: (sdk: SDKTypes.SingleSelectEnumSchema, spec: SpecTypes.SingleSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - UntitledMultiSelectEnumSchema: (sdk: SDKTypes.UntitledMultiSelectEnumSchema, spec: SpecTypes.UntitledMultiSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - TitledMultiSelectEnumSchema: (sdk: SDKTypes.TitledMultiSelectEnumSchema, spec: SpecTypes.TitledMultiSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - MultiSelectEnumSchema: (sdk: SDKTypes.MultiSelectEnumSchema, spec: SpecTypes.MultiSelectEnumSchema) => { - sdk = spec; - spec = sdk; - }, - LegacyTitledEnumSchema: (sdk: SDKTypes.LegacyTitledEnumSchema, spec: SpecTypes.LegacyTitledEnumSchema) => { - sdk = spec; - spec = sdk; - }, - PrimitiveSchemaDefinition: (sdk: SDKTypes.PrimitiveSchemaDefinition, spec: SpecTypes.PrimitiveSchemaDefinition) => { - sdk = spec; - spec = sdk; - }, - JSONRPCErrorResponse: (sdk: SDKTypes.JSONRPCErrorResponse, spec: SpecTypes.JSONRPCErrorResponse) => { - sdk = spec; - spec = sdk; - }, - JSONRPCResultResponse: (sdk: SDKTypes.JSONRPCResultResponse, spec: SpecTypes.JSONRPCResultResponse) => { - sdk = spec; - spec = sdk; - }, - JSONRPCMessage: (sdk: SDKTypes.JSONRPCMessage, spec: SpecTypes.JSONRPCMessage) => { - sdk = spec; - spec = sdk; - }, - CreateMessageRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.CreateMessageRequest) => { - sdk = spec; - spec = sdk; - }, - InitializeRequest: ( - sdk: WithJSONRPCRequest, - spec: FixSpecInitializeRequest - ) => { - sdk = spec; - spec = sdk; - }, - InitializeResult: (sdk: SDKTypes.InitializeResult, spec: FixSpecInitializeResult) => { - sdk = spec; - spec = sdk; - }, - ClientCapabilities: (sdk: SDKTypes.ClientCapabilities, spec: FixSpecClientCapabilities) => { - sdk = spec; - spec = sdk; - }, - ServerCapabilities: (sdk: SDKTypes.ServerCapabilities, spec: FixSpecServerCapabilities) => { - sdk = spec; - spec = sdk; - }, - ClientRequest: (sdk: WithJSONRPCRequest, spec: FixSpecClientRequest) => { - sdk = spec; - spec = sdk; - }, - ServerRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ServerRequest) => { - sdk = spec; - spec = sdk; - }, - LoggingMessageNotification: ( - sdk: MakeUnknownsNotOptional>, - spec: SpecTypes.LoggingMessageNotification - ) => { - sdk = spec; - spec = sdk; - }, - ServerNotification: (sdk: MakeUnknownsNotOptional>, spec: SpecTypes.ServerNotification) => { - sdk = spec; - spec = sdk; - }, - LoggingLevel: (sdk: SDKTypes.LoggingLevel, spec: SpecTypes.LoggingLevel) => { - sdk = spec; - spec = sdk; - }, - Icon: (sdk: SDKTypes.Icon, spec: SpecTypes.Icon) => { - sdk = spec; - spec = sdk; - }, - Icons: (sdk: SDKTypes.Icons, spec: SpecTypes.Icons) => { - sdk = spec; - spec = sdk; - }, - ModelHint: (sdk: SDKTypes.ModelHint, spec: SpecTypes.ModelHint) => { - sdk = spec; - spec = sdk; - }, - ModelPreferences: (sdk: SDKTypes.ModelPreferences, spec: SpecTypes.ModelPreferences) => { - sdk = spec; - spec = sdk; - }, - ToolChoice: (sdk: SDKTypes.ToolChoice, spec: SpecTypes.ToolChoice) => { - sdk = spec; - spec = sdk; - }, - ToolUseContent: (sdk: SDKTypes.ToolUseContent, spec: SpecTypes.ToolUseContent) => { - sdk = spec; - spec = sdk; - }, - ToolResultContent: (sdk: SDKTypes.ToolResultContent, spec: SpecTypes.ToolResultContent) => { - sdk = spec; - spec = sdk; - }, - SamplingMessageContentBlock: (sdk: SDKTypes.SamplingMessageContentBlock, spec: SpecTypes.SamplingMessageContentBlock) => { - sdk = spec; - spec = sdk; - }, - Annotations: (sdk: SDKTypes.Annotations, spec: SpecTypes.Annotations) => { - sdk = spec; - spec = sdk; - }, - Role: (sdk: SDKTypes.Role, spec: SpecTypes.Role) => { - sdk = spec; - spec = sdk; - }, - TaskAugmentedRequestParams: (sdk: SDKTypes.TaskAugmentedRequestParams, spec: SpecTypes.TaskAugmentedRequestParams) => { - sdk = spec; - spec = sdk; - }, - ToolExecution: (sdk: SDKTypes.ToolExecution, spec: SpecTypes.ToolExecution) => { - sdk = spec; - spec = sdk; - }, - TaskStatus: (sdk: SDKTypes.TaskStatus, spec: SpecTypes.TaskStatus) => { - sdk = spec; - spec = sdk; - }, - TaskMetadata: (sdk: SDKTypes.TaskMetadata, spec: SpecTypes.TaskMetadata) => { - sdk = spec; - spec = sdk; - }, - RelatedTaskMetadata: (sdk: SDKTypes.RelatedTaskMetadata, spec: SpecTypes.RelatedTaskMetadata) => { - sdk = spec; - spec = sdk; - }, - Task: (sdk: SDKTypes.Task, spec: SpecTypes.Task) => { - sdk = spec; - spec = sdk; - }, - CreateTaskResult: (sdk: SDKTypes.CreateTaskResult, spec: SpecTypes.CreateTaskResult) => { - sdk = spec; - spec = sdk; - }, - GetTaskResult: (sdk: SDKTypes.GetTaskResult, spec: SpecTypes.GetTaskResult) => { - sdk = spec; - spec = sdk; - }, - GetTaskPayloadRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.GetTaskPayloadRequest) => { - sdk = spec; - spec = sdk; - }, - ListTasksRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.ListTasksRequest) => { - sdk = spec; - spec = sdk; - }, - ListTasksResult: (sdk: SDKTypes.ListTasksResult, spec: SpecTypes.ListTasksResult) => { - sdk = spec; - spec = sdk; - }, - CancelTaskRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.CancelTaskRequest) => { - sdk = spec; - spec = sdk; - }, - CancelTaskResult: (sdk: SDKTypes.CancelTaskResult, spec: SpecTypes.CancelTaskResult) => { - sdk = spec; - spec = sdk; - }, - GetTaskRequest: (sdk: WithJSONRPCRequest, spec: SpecTypes.GetTaskRequest) => { - sdk = spec; - spec = sdk; - }, - GetTaskPayloadResult: (sdk: SDKTypes.GetTaskPayloadResult, spec: SpecTypes.GetTaskPayloadResult) => { - sdk = spec; - spec = sdk; - }, - TaskStatusNotificationParams: (sdk: SDKTypes.TaskStatusNotificationParams, spec: SpecTypes.TaskStatusNotificationParams) => { - sdk = spec; - spec = sdk; - }, - TaskStatusNotification: (sdk: WithJSONRPC, spec: SpecTypes.TaskStatusNotification) => { - sdk = spec; - spec = sdk; - } -}; - -// This file is .gitignore'd, and fetched by `npm run fetch:spec-types` (called by `npm run test`) -const SPEC_TYPES_FILE = 'src/spec.types.ts'; -const SDK_TYPES_FILE = 'src/types.ts'; - -const MISSING_SDK_TYPES = [ - // These are inlined in the SDK: - 'Error', // The inner error object of a JSONRPCError - 'URLElicitationRequiredError' // In the SDK, but with a custom definition -]; - -function extractExportedTypes(source: string): string[] { - return [...source.matchAll(/export\s+(?:interface|class|type)\s+(\w+)\b/g)].map(m => m[1]); -} - -describe('Spec Types', () => { - const specTypes = extractExportedTypes(fs.readFileSync(SPEC_TYPES_FILE, 'utf-8')); - const sdkTypes = extractExportedTypes(fs.readFileSync(SDK_TYPES_FILE, 'utf-8')); - const typesToCheck = specTypes.filter(type => !MISSING_SDK_TYPES.includes(type)); - - it('should define some expected types', () => { - expect(specTypes).toContain('JSONRPCNotification'); - expect(specTypes).toContain('ElicitResult'); - expect(specTypes).toHaveLength(145); - }); - - it('should have up to date list of missing sdk types', () => { - for (const typeName of MISSING_SDK_TYPES) { - expect(sdkTypes).not.toContain(typeName); - } - }); - - it('should have comprehensive compatibility tests', () => { - const missingTests = []; - - for (const typeName of typesToCheck) { - if (!sdkTypeChecks[typeName as keyof typeof sdkTypeChecks]) { - missingTests.push(typeName); - } - } - - expect(missingTests).toHaveLength(0); - }); - - describe('Missing SDK Types', () => { - it.each(MISSING_SDK_TYPES)('%s should not be present in MISSING_SDK_TYPES if it has a compatibility test', type => { - expect(sdkTypeChecks[type as keyof typeof sdkTypeChecks]).toBeUndefined(); - }); - }); -});