From b383847bf2c7f4f5fa37e3bd054205f71618e035 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 13:35:51 -0700 Subject: [PATCH 1/9] chore: prepare SDK v2 dependencies --- examples/basic-server-preact/package.json | 2 + examples/basic-server-react/package.json | 2 + examples/basic-server-solid/package.json | 2 + examples/basic-server-svelte/package.json | 2 + examples/basic-server-vanillajs/package.json | 2 + examples/basic-server-vue/package.json | 2 + examples/budget-allocator-server/package.json | 2 + examples/cohort-heatmap-server/package.json | 2 + .../customer-segmentation-server/package.json | 2 + examples/debug-server/package.json | 2 + examples/integration-server/package.json | 2 + examples/lazy-auth-server/package.json | 1 + examples/map-server/package.json | 2 + examples/pdf-server/package.json | 2 + examples/quickstart/package.json | 2 + examples/scenario-modeler-server/package.json | 2 + examples/shadertoy-server/package.json | 2 + examples/sheet-music-server/package.json | 2 + examples/system-monitor-server/package.json | 2 + examples/threejs-server/package.json | 2 + examples/transcript-server/package.json | 2 + examples/video-resource-server/package.json | 2 + examples/wiki-explorer-server/package.json | 2 + package-lock.json | 537 +++++------------- package.json | 10 +- 25 files changed, 187 insertions(+), 405 deletions(-) diff --git a/examples/basic-server-preact/package.json b/examples/basic-server-preact/package.json index 512720205..1a6323233 100644 --- a/examples/basic-server-preact/package.json +++ b/examples/basic-server-preact/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/basic-server-react/package.json b/examples/basic-server-react/package.json index e4d12333f..821fc9603 100644 --- a/examples/basic-server-react/package.json +++ b/examples/basic-server-react/package.json @@ -35,6 +35,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/basic-server-solid/package.json b/examples/basic-server-solid/package.json index 8f75ac8c2..997bc60f6 100644 --- a/examples/basic-server-solid/package.json +++ b/examples/basic-server-solid/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/basic-server-svelte/package.json b/examples/basic-server-svelte/package.json index f877bf7c8..e83772f20 100644 --- a/examples/basic-server-svelte/package.json +++ b/examples/basic-server-svelte/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/basic-server-vanillajs/package.json b/examples/basic-server-vanillajs/package.json index 5d47f2cdc..64e1758a5 100644 --- a/examples/basic-server-vanillajs/package.json +++ b/examples/basic-server-vanillajs/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/basic-server-vue/package.json b/examples/basic-server-vue/package.json index d27ddc91b..e4cd0b431 100644 --- a/examples/basic-server-vue/package.json +++ b/examples/basic-server-vue/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/budget-allocator-server/package.json b/examples/budget-allocator-server/package.json index 19a1d2554..8af1a6c6e 100644 --- a/examples/budget-allocator-server/package.json +++ b/examples/budget-allocator-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", diff --git a/examples/cohort-heatmap-server/package.json b/examples/cohort-heatmap-server/package.json index 8d1268b5c..99e9d9fa7 100644 --- a/examples/cohort-heatmap-server/package.json +++ b/examples/cohort-heatmap-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/customer-segmentation-server/package.json b/examples/customer-segmentation-server/package.json index 4511de903..59e3eb172 100644 --- a/examples/customer-segmentation-server/package.json +++ b/examples/customer-segmentation-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", diff --git a/examples/debug-server/package.json b/examples/debug-server/package.json index 3417a8fe9..02e9fbf70 100644 --- a/examples/debug-server/package.json +++ b/examples/debug-server/package.json @@ -35,6 +35,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.1.13" }, diff --git a/examples/integration-server/package.json b/examples/integration-server/package.json index f6715a4ff..3f9d8bb14 100644 --- a/examples/integration-server/package.json +++ b/examples/integration-server/package.json @@ -16,6 +16,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/lazy-auth-server/package.json b/examples/lazy-auth-server/package.json index f3ed6cd27..086f20a82 100644 --- a/examples/lazy-auth-server/package.json +++ b/examples/lazy-auth-server/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/map-server/package.json b/examples/map-server/package.json index 5dc23fe20..b0c629ace 100644 --- a/examples/map-server/package.json +++ b/examples/map-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/pdf-server/package.json b/examples/pdf-server/package.json index 4d960f175..aeb52c770 100644 --- a/examples/pdf-server/package.json +++ b/examples/pdf-server/package.json @@ -27,6 +27,8 @@ "dependencies": { "@cantoo/pdf-lib": "^2.6.5", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index 99948cc2d..100363786 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -16,6 +16,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0" diff --git a/examples/scenario-modeler-server/package.json b/examples/scenario-modeler-server/package.json index aa1e668ef..a7e8a9a63 100644 --- a/examples/scenario-modeler-server/package.json +++ b/examples/scenario-modeler-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", diff --git a/examples/shadertoy-server/package.json b/examples/shadertoy-server/package.json index ccf15a45c..0c275017b 100644 --- a/examples/shadertoy-server/package.json +++ b/examples/shadertoy-server/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/sheet-music-server/package.json b/examples/sheet-music-server/package.json index ca9fa909f..6cf175a99 100644 --- a/examples/sheet-music-server/package.json +++ b/examples/sheet-music-server/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "abcjs": "^6.4.4", "cors": "^2.8.5", diff --git a/examples/system-monitor-server/package.json b/examples/system-monitor-server/package.json index 12e8cea06..760d07fcc 100644 --- a/examples/system-monitor-server/package.json +++ b/examples/system-monitor-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", diff --git a/examples/threejs-server/package.json b/examples/threejs-server/package.json index cfb5686a6..9dda2886a 100644 --- a/examples/threejs-server/package.json +++ b/examples/threejs-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/transcript-server/package.json b/examples/transcript-server/package.json index dcba3d7c0..ce5b737e6 100644 --- a/examples/transcript-server/package.json +++ b/examples/transcript-server/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/video-resource-server/package.json b/examples/video-resource-server/package.json index b0d04aff1..605b8de11 100644 --- a/examples/video-resource-server/package.json +++ b/examples/video-resource-server/package.json @@ -25,6 +25,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/wiki-explorer-server/package.json b/examples/wiki-explorer-server/package.json index fe511ad0f..62f1de8ad 100644 --- a/examples/wiki-explorer-server/package.json +++ b/examples/wiki-explorer-server/package.json @@ -27,6 +27,8 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/express": "2.0.0-beta.4", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cheerio": "^1.0.0", "cors": "^2.8.5", diff --git a/package-lock.json b/package-lock.json index 29e905875..5514bf1db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,10 @@ }, "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", + "@modelcontextprotocol/client": "2.0.0-beta.4", + "@modelcontextprotocol/core": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -44,16 +47,19 @@ "typedoc": "^0.28.14", "typedoc-github-theme": "^0.4.0", "typescript": "^5.9.3", - "zod": "^4.1.13" + "zod": "^4.2.0" }, "engines": { "node": ">=20" }, "peerDependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", + "@modelcontextprotocol/core": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" + "zod": "^4.2.0" }, "peerDependenciesMeta": { "react": { @@ -89,29 +95,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-host/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-host/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-preact": { "name": "@modelcontextprotocol/server-basic-preact", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -133,29 +124,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-server-preact/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-preact/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-react": { "name": "@modelcontextprotocol/server-basic-react", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -180,29 +156,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-server-react/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-react/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-solid": { "name": "@modelcontextprotocol/server-basic-solid", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -224,29 +185,14 @@ "vite-plugin-solid": "^2.11.12" } }, - "examples/basic-server-solid/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-solid/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-svelte": { "name": "@modelcontextprotocol/server-basic-svelte", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -268,29 +214,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-server-svelte/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-svelte/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-vanillajs": { "name": "@modelcontextprotocol/server-basic-vanillajs", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -310,29 +241,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-server-vanillajs/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-vanillajs/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/basic-server-vue": { "name": "@modelcontextprotocol/server-basic-vue", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -354,29 +270,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/basic-server-vue/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/basic-server-vue/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/budget-allocator-server": { "name": "@modelcontextprotocol/server-budget-allocator", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", @@ -397,29 +298,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/budget-allocator-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/budget-allocator-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/cohort-heatmap-server": { "name": "@modelcontextprotocol/server-cohort-heatmap", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -444,29 +330,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/cohort-heatmap-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/cohort-heatmap-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/customer-segmentation-server": { "name": "@modelcontextprotocol/server-customer-segmentation", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", @@ -487,29 +358,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/customer-segmentation-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/customer-segmentation-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/debug-server": { "name": "@modelcontextprotocol/server-debug", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.1.13" }, @@ -529,27 +385,12 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/debug-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/debug-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/integration-server": { "version": "1.7.4", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -573,29 +414,13 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/integration-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/integration-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/lazy-auth-server": { "name": "@modelcontextprotocol/server-lazy-auth", "version": "1.7.4", "license": "MIT", "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -615,29 +440,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/lazy-auth-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/lazy-auth-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/map-server": { "name": "@modelcontextprotocol/server-map", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -657,30 +467,15 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/map-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/map-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/pdf-server": { "name": "@modelcontextprotocol/server-pdf", "version": "1.7.4", "license": "MIT", "dependencies": { "@cantoo/pdf-lib": "^2.6.5", + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -701,23 +496,6 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/pdf-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/pdf-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/qr-server": { "name": "@modelcontextprotocol/server-qr", "version": "1.7.4", @@ -731,7 +509,9 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0" @@ -748,16 +528,6 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/quickstart/node_modules/@types/node": { - "version": "22.19.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", - "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, "examples/say-server": { "name": "@modelcontextprotocol/server-say", "version": "1.7.4", @@ -772,7 +542,9 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", @@ -798,29 +570,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/scenario-modeler-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/scenario-modeler-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/shadertoy-server": { "name": "@modelcontextprotocol/server-shadertoy", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -840,29 +597,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/shadertoy-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/shadertoy-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/sheet-music-server": { "name": "@modelcontextprotocol/server-sheet-music", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "abcjs": "^6.4.4", "cors": "^2.8.5", @@ -883,29 +625,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/sheet-music-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/sheet-music-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/system-monitor-server": { "name": "@modelcontextprotocol/server-system-monitor", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", @@ -927,29 +654,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/system-monitor-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/system-monitor-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/threejs-server": { "name": "@modelcontextprotocol/server-threejs", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -976,29 +688,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/threejs-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/threejs-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/transcript-server": { "name": "@modelcontextprotocol/server-transcript", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -1019,29 +716,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/transcript-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/transcript-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/video-resource-server": { "name": "@modelcontextprotocol/server-video-resource", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -1061,29 +743,14 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/video-resource-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/video-resource-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "examples/wiki-explorer-server": { "name": "@modelcontextprotocol/server-wiki-explorer", "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/node": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", "cheerio": "^1.0.0", "cors": "^2.8.5", @@ -1105,23 +772,6 @@ "vite-plugin-singlefile": "^2.3.0" } }, - "examples/wiki-explorer-server/node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "examples/wiki-explorer-server/node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -2576,6 +2226,53 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0-beta.4.tgz", + "integrity": "sha512-VNHA/UXDk7mCpVl+jOg5B4WMRRD2OEl+it360Lhi6HiCbrIB2f6pZ0cqSDKXQVUtZvqnhdQHzZAM9h8EKWhq/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.0.0-beta.4", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0-beta.4.tgz", + "integrity": "sha512-nsMXd4wQBKzmph6r+WOhum+mXjDYljTAqwY/XUg3hLtvNOQ8+JVqBSJOVCMJvx9lXhTpTOPrGZ3BuNiaNPjSvg==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/express": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/express/-/express-2.0.0-beta.4.tgz", + "integrity": "sha512-7hI8bEomz6rMn8sKYRVIZgQJsrWIjIS9WiPyIp3X2pMyLSCC7hB5km0kg+9SygWygTIRnPfte0tXevRB3mXEHQ==", + "license": "MIT", + "dependencies": { + "cors": "^2.8.5" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0-beta.4", + "express": "^4.18.0 || ^5.0.0" + } + }, "node_modules/@modelcontextprotocol/ext-apps": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.1.tgz", @@ -2609,6 +2306,27 @@ "resolved": "examples/basic-host", "link": true }, + "node_modules/@modelcontextprotocol/node": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0-beta.4.tgz", + "integrity": "sha512-OqbrRVNYNfLxBB2BRkP5oIHKHa0IPHluMbaLgw7YCRKcj5be9ExzyWFJY3KVMb+1F2AVn+0SRoH0XKcNzh3HYQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0-beta.4", + "hono": "^4.11.4" + }, + "peerDependenciesMeta": { + "hono": { + "optional": true + } + } + }, "node_modules/@modelcontextprotocol/quickstart": { "resolved": "examples/quickstart", "link": true @@ -2653,6 +2371,19 @@ } } }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0-beta.4.tgz", + "integrity": "sha512-pjMZcNEt1dOq0aJCcY3b7w1Ayh+qmQN2xlfTELcGV9yiAjEGIczBPBLWWW0k0zzo5T8kiv15jtKPsWeHGxLvLg==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.0.0-beta.4", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@modelcontextprotocol/server-basic-preact": { "resolved": "examples/basic-server-preact", "link": true diff --git a/package.json b/package.json index 8c6ead57b..35bcdf077 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,10 @@ "author": "Olivier Chafik", "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", + "@modelcontextprotocol/client": "2.0.0-beta.4", + "@modelcontextprotocol/core": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -105,13 +108,16 @@ "typedoc": "^0.28.14", "typedoc-github-theme": "^0.4.0", "typescript": "^5.9.3", - "zod": "^4.1.13" + "zod": "^4.2.0" }, "peerDependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", + "@modelcontextprotocol/core": "2.0.0-beta.4", "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" + "zod": "^4.2.0" }, "peerDependenciesMeta": { "react": { From 209cfc62413b7eea46c127ddd756ce58bd2da095 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 13:49:10 -0700 Subject: [PATCH 2/9] refactor: compose protocol-neutral event dispatch --- src/app-bridge.test.ts | 2 +- src/app-bridge.ts | 120 ++++++++++++++++--- src/app.ts | 139 +++++++++++++++++----- src/events.test.ts | 82 +++++++++++++ src/events.ts | 261 ++++++----------------------------------- typedoc.config.mjs | 2 +- 6 files changed, 335 insertions(+), 271 deletions(-) create mode 100644 src/events.test.ts diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index a4fe1de1e..7adbb16d0 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -2713,7 +2713,7 @@ describe("isToolVisibilityAppOnly", () => { expect(a).toEqual([]); }); - it("App.onEventDispatch merges hostcontext before listeners fire", async () => { + it("App merges hostcontext before listeners fire", async () => { let seen: unknown; app.addEventListener("hostcontextchanged", () => { seen = app.getHostContext(); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 23383c40f..18d76718f 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -47,7 +47,10 @@ import { ProtocolOptions, RequestOptions, } from "@modelcontextprotocol/sdk/shared/protocol.js"; -import { ProtocolWithEvents } from "./events"; +import { EventDispatcher } from "./events"; +import type { ZodLiteral, ZodObject } from "zod/v4"; + +type MethodSchema = ZodObject<{ method: ZodLiteral }>; import { type AppNotification, @@ -303,16 +306,36 @@ export type AppBridgeEventMap = { * await bridge.connect(transport); * ``` */ -export class AppBridge extends ProtocolWithEvents< - AppRequest, - AppNotification, - AppResult, - AppBridgeEventMap -> { +export class AppBridge extends Protocol { private _appCapabilities?: McpUiAppCapabilities; private _hostContext: McpUiHostContext = {}; private _appInfo?: Implementation; private _initializedReceived = false; + private readonly _registeredMethods = new Set(); + private readonly _registeredEvents = new Set(); + private readonly _events = new EventDispatcher(); + + // ProtocolWithEvents previously owned these registration guards. Keep them + // on AppBridge to preserve the existing double-registration behavior while + // event fan-out moves to protocol-independent composition. Arrow-function + // fields let Protocol's constructor run before the guards initialize. + override setRequestHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setRequestHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setRequestHandler"); + super.setRequestHandler(schema, handler); + }; + + override setNotificationHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setNotificationHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setNotificationHandler"); + super.setNotificationHandler(schema, handler); + }; /** * Wrap every handler registered via `replaceRequestHandler` with a check @@ -324,13 +347,14 @@ export class AppBridge extends ProtocolWithEvents< * * @see {@link https://github.com/anthropics/claude-ai-mcp/issues/149 claude-ai-mcp#149} */ - private _baseReplaceRequestHandler = this.replaceRequestHandler; - protected override replaceRequestHandler: Protocol< + protected replaceRequestHandler: Protocol< AppRequest, AppNotification, AppResult >["setRequestHandler"] = (schema, handler) => { - this._baseReplaceRequestHandler(schema, (request, extra) => { + const method = (schema as MethodSchema).shape.method.value; + this._registeredMethods.add(method); + super.setRequestHandler(schema, (request, extra) => { if (!this._initializedReceived) { console.warn( `[ext-apps] AppBridge received '${request.method}' before ` + @@ -343,7 +367,9 @@ export class AppBridge extends ProtocolWithEvents< }); }; - protected readonly eventSchemas = { + private readonly eventSchemas: { + [K in keyof AppBridgeEventMap]: MethodSchema; + } = { sizechange: McpUiSizeChangedNotificationSchema, sandboxready: McpUiSandboxProxyReadyNotificationSchema, initialized: McpUiInitializedNotificationSchema, @@ -351,6 +377,74 @@ export class AppBridge extends ProtocolWithEvents< loggingmessage: LoggingMessageNotificationSchema, }; + protected setEventHandler( + event: K, + handler: ((params: AppBridgeEventMap[K]) => void) | undefined, + ): void { + this._ensureEventSlot(event); + this._events.setHandler(event, handler); + } + + protected getEventHandler( + event: K, + ): ((params: AppBridgeEventMap[K]) => void) | undefined { + return this._events.getHandler(event); + } + + addEventListener( + event: K, + handler: (params: AppBridgeEventMap[K]) => void, + ): void { + this._ensureEventSlot(event); + this._events.addEventListener(event, handler); + } + + removeEventListener( + event: K, + handler: (params: AppBridgeEventMap[K]) => void, + ): void { + this._events.removeEventListener(event, handler); + } + + private _ensureEventSlot( + event: K, + ): void { + if (this._registeredEvents.has(event)) return; + const schema = this.eventSchemas[event]; + if (!schema) throw new Error(`Unknown event: ${String(event)}`); + this.setNotificationHandler(schema, (notification) => { + const params = (notification as { params: AppBridgeEventMap[K] }).params; + if (event === "initialized") this._initializedReceived = true; + this._events.dispatch(event, params); + }); + this._registeredEvents.add(event); + } + + protected warnIfRequestHandlerReplaced( + name: string, + previous: unknown, + next: unknown, + ): void { + if (previous && next) { + console.warn( + `[MCP Apps] ${name} handler replaced. ` + + `Previous handler will no longer be called.`, + ); + } + } + + private _assertMethodNotRegistered(schema: unknown, via: string): void { + const method = (schema as MethodSchema).shape.method.value; + if (this._registeredMethods.has(method)) { + throw new Error( + `Handler for "${method}" already registered (via ${via}). ` + + `Use addEventListener() to attach multiple listeners, ` + + `or the on* setter for replace semantics.`, + ); + } + this._registeredMethods.add(method); + } + /** * Create a new AppBridge instance. * @@ -392,9 +486,7 @@ export class AppBridge extends ProtocolWithEvents< ) { super(options); - this.addEventListener("initialized", () => { - this._initializedReceived = true; - }); + this._ensureEventSlot("initialized"); this._hostContext = options?.hostContext || {}; diff --git a/src/app.ts b/src/app.ts index adfad5c77..c586c3127 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,7 @@ import { type RequestOptions, mergeCapabilities, + Protocol, ProtocolOptions, } from "@modelcontextprotocol/sdk/shared/protocol.js"; @@ -32,8 +33,8 @@ import { ToolListChangedNotification, } from "@modelcontextprotocol/sdk/types.js"; import { AppNotification, AppRequest, AppResult } from "./types"; -import { ProtocolWithEvents } from "./events"; -export { ProtocolWithEvents }; +import { EventDispatcher } from "./events"; +export { EventDispatcher } from "./events"; import { PostMessageTransport } from "./message-transport"; import { LATEST_PROTOCOL_VERSION, @@ -74,7 +75,9 @@ import { standardSchemaToJsonSchema, validateStandardSchema, } from "./standard-schema"; -import { z } from "zod/v4"; +import { z, type ZodLiteral, type ZodObject } from "zod/v4"; + +type MethodSchema = ZodObject<{ method: ZodLiteral }>; export type { StandardSchemaV1, @@ -271,9 +274,9 @@ export type RegisteredAppTool = { /** * Maps DOM-style event names to their notification `params` types. * - * Used by {@link App `App`} (which extends {@link ProtocolWithEvents `ProtocolWithEvents`}) - * to provide type-safe `addEventListener` / `removeEventListener` and - * singular `on*` handler support. + * Used by {@link App `App`} and its composed {@link EventDispatcher} to + * provide type-safe `addEventListener` / `removeEventListener` and singular + * `on*` handler support. */ export type AppEventMap = { toolinput: McpUiToolInputNotification["params"]; @@ -304,16 +307,14 @@ export type AppEventMap = { * 3. **Interactive**: Send requests, receive notifications, call tools * 4. **Teardown**: Host sends teardown request before unmounting * - * ## Inherited Methods + * ## Protocol and event methods * - * As a subclass of {@link ProtocolWithEvents `ProtocolWithEvents`}, `App` inherits: + * As a subclass of the MCP SDK's `Protocol`, `App` provides: * - `setRequestHandler()` - Register handlers for requests from host * - `setNotificationHandler()` - Register handlers for notifications from host * - `addEventListener()` - Append a listener for a notification event (multi-listener) * - `removeEventListener()` - Remove a previously added listener * - * @see {@link ProtocolWithEvents `ProtocolWithEvents`} for the DOM-model event system - * * ## Notification Setters (DOM-model `on*` handlers) * * For common notifications, the `App` class provides getter/setter properties @@ -342,17 +343,37 @@ export type AppEventMap = { * await app.connect(); * ``` */ -export class App extends ProtocolWithEvents< - AppRequest, - AppNotification, - AppResult, - AppEventMap -> { +export class App extends Protocol { private _hostCapabilities?: McpUiHostCapabilities; private _hostInfo?: Implementation; private _hostContext?: McpUiHostContext; private _registeredTools: { [name: string]: RegisteredAppTool } = {}; private _initializedSent = false; + private readonly _registeredMethods = new Set(); + private readonly _registeredEvents = new Set(); + private readonly _events = new EventDispatcher(); + + // ProtocolWithEvents previously owned these registration guards. Keep them + // on App to preserve the existing double-registration behavior while event + // fan-out moves to protocol-independent composition. Arrow-function fields + // ensure Protocol's constructor uses its own methods before the guards init. + override setRequestHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setRequestHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setRequestHandler"); + super.setRequestHandler(schema, handler); + }; + + override setNotificationHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setNotificationHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setNotificationHandler"); + super.setNotificationHandler(schema, handler); + }; /** * Warn if a host-bound method is called before {@link connect `connect`} has @@ -378,7 +399,9 @@ export class App extends ProtocolWithEvents< console.warn(`${msg}. This will throw in a future release.`); } - protected readonly eventSchemas = { + private readonly eventSchemas: { + [K in keyof AppEventMap]: MethodSchema; + } = { toolinput: McpUiToolInputNotificationSchema, toolinputpartial: McpUiToolInputPartialNotificationSchema, toolresult: McpUiToolResultNotificationSchema, @@ -427,29 +450,84 @@ export class App extends ProtocolWithEvents< console.warn(msg); } - protected override setEventHandler( + protected setEventHandler( event: K, handler: ((params: AppEventMap[K]) => void) | undefined, ): void { if (handler) this._assertHandlerTiming(event); - super.setEventHandler(event, handler); + this._ensureEventSlot(event); + this._events.setHandler(event, handler); } - override addEventListener( + addEventListener( event: K, handler: (params: AppEventMap[K]) => void, ): void { this._assertHandlerTiming(event); - super.addEventListener(event, handler); + this._ensureEventSlot(event); + this._events.addEventListener(event, handler); } - protected override onEventDispatch( + removeEventListener( event: K, - params: AppEventMap[K], + handler: (params: AppEventMap[K]) => void, ): void { - if (event === "hostcontextchanged") { - this._hostContext = { ...this._hostContext, ...params }; + this._events.removeEventListener(event, handler); + } + + private _ensureEventSlot(event: K): void { + if (this._registeredEvents.has(event)) return; + const schema = this.eventSchemas[event]; + if (!schema) throw new Error(`Unknown event: ${String(event)}`); + this.setNotificationHandler(schema, (notification) => { + const params = (notification as { params: AppEventMap[K] }).params; + if (event === "hostcontextchanged") { + this._hostContext = { ...this._hostContext, ...params }; + } + this._events.dispatch(event, params); + }); + this._registeredEvents.add(event); + } + + protected getEventHandler( + event: K, + ): ((params: AppEventMap[K]) => void) | undefined { + return this._events.getHandler(event); + } + + protected warnIfRequestHandlerReplaced( + name: string, + previous: unknown, + next: unknown, + ): void { + if (previous && next) { + console.warn( + `[MCP Apps] ${name} handler replaced. ` + + `Previous handler will no longer be called.`, + ); + } + } + + protected replaceRequestHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setRequestHandler"] = (schema, handler) => { + const method = (schema as MethodSchema).shape.method.value; + this._registeredMethods.add(method); + super.setRequestHandler(schema, handler); + }; + + private _assertMethodNotRegistered(schema: unknown, via: string): void { + const method = (schema as MethodSchema).shape.method.value; + if (this._registeredMethods.has(method)) { + throw new Error( + `Handler for "${method}" already registered (via ${via}). ` + + `Use addEventListener() to attach multiple listeners, ` + + `or the on* setter for replace semantics.`, + ); } + this._registeredMethods.add(method); } /** @@ -484,9 +562,8 @@ export class App extends ProtocolWithEvents< return {}; }); - // Eagerly register the hostcontextchanged event slot so that - // onEventDispatch (which merges into _hostContext) fires even if the - // user never assigns onhostcontextchanged or calls addEventListener. + // Eagerly register hostcontextchanged so the owner-side context merge runs + // even if the user never assigns a handler or adds a listener. this.setEventHandler("hostcontextchanged", undefined); } @@ -924,9 +1001,9 @@ export class App extends ProtocolWithEvents< * without replacing. * * Notification params are automatically merged into the internal host context - * via {@link onEventDispatch `onEventDispatch`} before any handler or listener - * fires. This means {@link getHostContext `getHostContext`} will return the - * updated values even before your callback runs. + * before any handler or listener fires. This means + * {@link getHostContext `getHostContext`} will return the updated values even + * before your callback runs. * * Register handlers before calling {@link connect `connect`} to avoid missing notifications. * diff --git a/src/events.test.ts b/src/events.test.ts new file mode 100644 index 000000000..7fd87087f --- /dev/null +++ b/src/events.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, spyOn } from "bun:test"; + +import { EventDispatcher } from "./events"; + +type TestEventMap = { + change: { value: number }; +}; + +describe("EventDispatcher", () => { + it("replaces the singular handler", () => { + const dispatcher = new EventDispatcher(); + const calls: string[] = []; + const first = () => calls.push("first"); + const second = () => calls.push("second"); + const warn = spyOn(console, "warn").mockImplementation(() => {}); + + try { + dispatcher.setHandler("change", first); + dispatcher.setHandler("change", second); + + expect(dispatcher.getHandler("change")).toBe(second); + dispatcher.dispatch("change", { value: 1 }); + expect(calls).toEqual(["second"]); + } finally { + warn.mockRestore(); + } + }); + + it("dispatches the singular handler before listeners in insertion order", () => { + const dispatcher = new EventDispatcher(); + const calls: string[] = []; + + dispatcher.setHandler("change", () => calls.push("handler")); + dispatcher.addEventListener("change", () => calls.push("listener-1")); + dispatcher.addEventListener("change", () => calls.push("listener-2")); + + dispatcher.dispatch("change", { value: 1 }); + + expect(calls).toEqual(["handler", "listener-1", "listener-2"]); + }); + + it("uses a listener snapshot when a listener is removed during dispatch", () => { + const dispatcher = new EventDispatcher(); + const calls: string[] = []; + const second = () => calls.push("second"); + const first = () => { + calls.push("first"); + dispatcher.removeEventListener("change", second); + }; + + dispatcher.addEventListener("change", first); + dispatcher.addEventListener("change", second); + + dispatcher.dispatch("change", { value: 1 }); + dispatcher.dispatch("change", { value: 2 }); + + expect(calls).toEqual(["first", "second", "first"]); + }); + + it("warns only when one singular handler replaces another", () => { + const dispatcher = new EventDispatcher(); + const warn = spyOn(console, "warn").mockImplementation(() => {}); + const first = () => {}; + const second = () => {}; + + try { + dispatcher.setHandler("change", first); + dispatcher.setHandler("change", undefined); + dispatcher.setHandler("change", first); + expect(warn).not.toHaveBeenCalled(); + + dispatcher.setHandler("change", second); + + expect(warn).toHaveBeenCalledTimes(1); + expect(String(warn.mock.calls[0]?.[0])).toContain( + "onchange handler replaced", + ); + } finally { + warn.mockRestore(); + } + }); +}); diff --git a/src/events.ts b/src/events.ts index 4d17ca4f2..b89186713 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,17 +1,6 @@ -import { Protocol } from "@modelcontextprotocol/sdk/shared/protocol.js"; -import { - Request, - Notification, - Result, -} from "@modelcontextprotocol/sdk/types.js"; -import { ZodLiteral, ZodObject } from "zod/v4"; - -type MethodSchema = ZodObject<{ method: ZodLiteral }>; - /** - * Per-event state: a singular `on*` handler (replace semantics) plus a - * listener array (`addEventListener` semantics), mirroring the DOM model - * where `el.onclick` and `el.addEventListener("click", …)` coexist. + * Per-event state: a singular `on*` handler (replace semantics) plus an + * ordered listener array (`addEventListener` semantics). */ interface EventSlot { onHandler?: ((params: T) => void) | undefined; @@ -19,118 +8,19 @@ interface EventSlot { } /** - * Intermediate base class that adds DOM-style event support on top of the - * MCP SDK's `Protocol`. - * - * The base `Protocol` class stores one handler per method: - * `setRequestHandler()` and `setNotificationHandler()` replace any existing - * handler for the same method silently. This class introduces a two-channel - * event model inspired by the DOM: - * - * ### Singular `on*` handler (like `el.onclick`) - * - * Subclasses expose `get`/`set` pairs that delegate to - * {@link setEventHandler `setEventHandler`} / - * {@link getEventHandler `getEventHandler`}. Assigning replaces the previous - * handler; assigning `undefined` clears it. `addEventListener` listeners are - * unaffected. - * - * ### Multi-listener (`addEventListener` / `removeEventListener`) - * - * Append to a per-event listener array. Listeners fire in insertion order - * after the singular `on*` handler. - * - * ### Dispatch order - * - * When a notification arrives for a mapped event: - * 1. {@link onEventDispatch `onEventDispatch`} (subclass side-effects) - * 2. The singular `on*` handler (if set) - * 3. All `addEventListener` listeners in insertion order - * - * ### Double-set protection + * Protocol-independent DOM-style event fan-out. * - * Direct calls to {@link setRequestHandler `setRequestHandler`} / - * {@link setNotificationHandler `setNotificationHandler`} throw if a handler - * for the same method has already been registered (through any path), so - * accidental overwrites surface as errors instead of silent bugs. + * Owners perform event-specific state updates before calling + * {@link dispatch}. Dispatch then invokes the singular handler followed by a + * snapshot of listeners in insertion order. * - * @typeParam EventMap - Maps event names to the listener's `params` type. + * @typeParam EventMap - Maps event names to their listener parameter types. */ -export abstract class ProtocolWithEvents< - SendRequestT extends Request, - SendNotificationT extends Notification, - SendResultT extends Result, - EventMap extends Record, -> extends Protocol { - private _registeredMethods = new Set(); - private _eventSlots = new Map(); +export class EventDispatcher> { + private readonly _eventSlots = new Map(); - /** - * Event name → notification schema. Subclasses populate this so that - * the event system can lazily register a dispatcher with the correct - * schema on first use. - */ - protected abstract readonly eventSchemas: { - [K in keyof EventMap]: MethodSchema; - }; - - /** - * Called once per incoming notification, before any handlers or listeners - * fire. Subclasses may override to perform side effects such as merging - * notification params into cached state. - */ - protected onEventDispatch( - _event: K, - _params: EventMap[K], - ): void {} - - // ── Event system (DOM model) ──────────────────────────────────────── - - /** - * Lazily create the event slot and register a single dispatcher with the - * base `Protocol`. The dispatcher fans out to the `on*` handler and all - * `addEventListener` listeners. - */ - private _ensureEventSlot( - event: K, - ): EventSlot { - let slot = this._eventSlots.get(event) as - | EventSlot - | undefined; - if (!slot) { - const schema = this.eventSchemas[event]; - if (!schema) { - throw new Error(`Unknown event: ${String(event)}`); - } - slot = { listeners: [] }; - this._eventSlots.set(event, slot as EventSlot); - - // Claim this method so direct setNotificationHandler calls throw. - const method = schema.shape.method.value; - this._registeredMethods.add(method); - - const s = slot; // stable reference for the closure - super.setNotificationHandler(schema, (n) => { - const params = (n as { params: EventMap[K] }).params; - this.onEventDispatch(event, params); - // 1. Singular on* handler - s.onHandler?.(params); - // 2. addEventListener listeners — snapshot to tolerate removal during - // dispatch (e.g., a listener that calls removeEventListener on itself) - for (const l of [...s.listeners]) l(params); - }); - } - return slot; - } - - /** - * Set or clear the singular `on*` handler for an event. - * - * Replace semantics — like the DOM's `el.onclick = fn`. Assigning - * `undefined` clears the handler without affecting `addEventListener` - * listeners. - */ - protected setEventHandler( + /** Set, replace, or clear the singular handler for an event. */ + setHandler( event: K, handler: ((params: EventMap[K]) => void) | undefined, ): void { @@ -144,31 +34,16 @@ export abstract class ProtocolWithEvents< slot.onHandler = handler; } - /** - * Get the singular `on*` handler for an event, or `undefined` if none is - * set. `addEventListener` listeners are not reflected here. - */ - protected getEventHandler( + /** Get the singular handler for an event, if one is set. */ + getHandler( event: K, ): ((params: EventMap[K]) => void) | undefined { - return (this._eventSlots.get(event) as EventSlot | undefined) - ?.onHandler; + return (this._eventSlots.get(event) as + | EventSlot + | undefined)?.onHandler; } - /** - * Add a listener for a notification event. - * - * Unlike the singular `on*` handler, calling this multiple times appends - * listeners rather than replacing them. All registered listeners fire in - * insertion order after the `on*` handler when the notification arrives. - * - * Registration is lazy: the first call (for a given event, from either - * this method or the `on*` setter) registers a dispatcher with the base - * `Protocol`. - * - * @param event - Event name (a key of the `EventMap` type parameter). - * @param handler - Listener invoked with the notification `params`. - */ + /** Add a listener. Listeners run in insertion order after the handler. */ addEventListener( event: K, handler: (params: EventMap[K]) => void, @@ -176,11 +51,7 @@ export abstract class ProtocolWithEvents< this._ensureEventSlot(event).listeners.push(handler); } - /** - * Remove a previously registered event listener. The dispatcher stays - * registered even if the listener array becomes empty; future - * notifications simply have no listeners to call. - */ + /** Remove the first matching listener, if present. */ removeEventListener( event: K, handler: (params: EventMap[K]) => void, @@ -193,87 +64,29 @@ export abstract class ProtocolWithEvents< if (idx !== -1) slot.listeners.splice(idx, 1); } - // ── Handler registration with double-set protection ───────────────── - - // The two overrides below are arrow-function class fields rather than - // prototype methods so that Protocol's constructor — which registers its - // own ping/cancelled/progress handlers via `this.setRequestHandler` - // before our fields initialize — hits the base implementation and skips - // tracking. Converting these to proper methods would crash with - // `_registeredMethods` undefined during super(). - - /** - * Registers a request handler. Throws if a handler for the same method - * has already been registered — use the `on*` setter (replace semantics) - * or `addEventListener` (multi-listener) for notification events. - * - * @throws {Error} if a handler for this method is already registered. - */ - override setRequestHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setRequestHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setRequestHandler"); - super.setRequestHandler(schema, handler); - }; - - /** - * Registers a notification handler. Throws if a handler for the same - * method has already been registered — use the `on*` setter (replace - * semantics) or `addEventListener` (multi-listener) for mapped events. - * - * @throws {Error} if a handler for this method is already registered. - */ - override setNotificationHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setNotificationHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setNotificationHandler"); - super.setNotificationHandler(schema, handler); - }; - /** - * Warn if a request handler `on*` setter is replacing a previously-set - * handler. Call from each request setter before updating the backing field. + * Dispatch an event to its singular handler and a listener snapshot. + * Listener mutations during dispatch take effect on the next dispatch. */ - protected warnIfRequestHandlerReplaced( - name: string, - previous: unknown, - next: unknown, - ): void { - if (previous && next) { - console.warn( - `[MCP Apps] ${name} handler replaced. ` + - `Previous handler will no longer be called.`, - ); - } + dispatch(event: K, params: EventMap[K]): void { + const slot = this._eventSlots.get(event) as + | EventSlot + | undefined; + if (!slot) return; + slot.onHandler?.(params); + for (const l of [...slot.listeners]) l(params); } - /** - * Replace a request handler, bypassing double-set protection. Used by - * `on*` request-handler setters that need replace semantics. - */ - protected replaceRequestHandler: Protocol< - SendRequestT, - SendNotificationT, - SendResultT - >["setRequestHandler"] = (schema, handler) => { - const method = (schema as MethodSchema).shape.method.value; - this._registeredMethods.add(method); - super.setRequestHandler(schema, handler); - }; - - private _assertMethodNotRegistered(schema: unknown, via: string): void { - const method = (schema as MethodSchema).shape.method.value; - if (this._registeredMethods.has(method)) { - throw new Error( - `Handler for "${method}" already registered (via ${via}). ` + - `Use addEventListener() to attach multiple listeners, ` + - `or the on* setter for replace semantics.`, - ); + private _ensureEventSlot( + event: K, + ): EventSlot { + let slot = this._eventSlots.get(event) as + | EventSlot + | undefined; + if (!slot) { + slot = { listeners: [] }; + this._eventSlots.set(event, slot as EventSlot); } - this._registeredMethods.add(method); + return slot; } } diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 4c5baa320..00babc403 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -28,7 +28,7 @@ const config = { ], excludePrivate: true, excludeInternal: false, - intentionallyNotExported: ["MethodSchema", "RequestHandlerExtra"], + intentionallyNotExported: ["RequestHandlerExtra"], blockTags: [...OptionDefaults.blockTags, "@description"], jsDocCompatibility: { exampleTag: false, From 59e6ffc6d8822f5139efc4d09fae46350761ebd2 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 15:02:25 -0700 Subject: [PATCH 3/9] refactor: migrate App to SDK v2 client --- src/app-bridge.test.ts | 44 +++--- src/app.test.ts | 300 +++++++++++++++++++++++++++++++++++++++++ src/app.ts | 263 ++++++++++++++++-------------------- 3 files changed, 435 insertions(+), 172 deletions(-) create mode 100644 src/app.test.ts diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 7adbb16d0..8f432b428 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -7,10 +7,7 @@ import { ListPromptsResultSchema, ListResourcesResultSchema, ListResourceTemplatesResultSchema, - PromptListChangedNotificationSchema, ReadResourceResultSchema, - ResourceListChangedNotificationSchema, - ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod/v4"; @@ -2313,9 +2310,12 @@ describe("App <-> AppBridge integration", () => { it("sendToolListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - app.setNotificationHandler(ToolListChangedNotificationSchema, (n) => { - receivedNotifications.push(n.params); - }); + app.setNotificationHandler( + "notifications/tools/list_changed", + (notification) => { + receivedNotifications.push(notification.params); + }, + ); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2328,9 +2328,12 @@ describe("App <-> AppBridge integration", () => { it("sendResourceListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - app.setNotificationHandler(ResourceListChangedNotificationSchema, (n) => { - receivedNotifications.push(n.params); - }); + app.setNotificationHandler( + "notifications/resources/list_changed", + (notification) => { + receivedNotifications.push(notification.params); + }, + ); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2343,9 +2346,12 @@ describe("App <-> AppBridge integration", () => { it("sendPromptListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - app.setNotificationHandler(PromptListChangedNotificationSchema, (n) => { - receivedNotifications.push(n.params); - }); + app.setNotificationHandler( + "notifications/prompts/list_changed", + (notification) => { + receivedNotifications.push(notification.params); + }, + ); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2771,20 +2777,6 @@ describe("isToolVisibilityAppOnly", () => { ); }).toThrow(/already registered/); }); - - it("direct setNotificationHandler throws for event-mapped methods", () => { - const app2 = new App(testAppInfo, {}, { autoResize: false }); - app2.addEventListener("toolinput", () => {}); - expect(() => { - app2.setNotificationHandler( - // @ts-expect-error — exercising throw path with raw schema - { - shape: { method: { value: "ui/notifications/tool-input" } }, - }, - () => {}, - ); - }).toThrow(/already registered/); - }); }); }); diff --git a/src/app.test.ts b/src/app.test.ts new file mode 100644 index 000000000..dcf2d5287 --- /dev/null +++ b/src/app.test.ts @@ -0,0 +1,300 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { + InMemoryTransport, + Server, + type JSONRPCMessage, +} from "@modelcontextprotocol/server"; +import { z } from "zod/v4"; + +import { App } from "./app"; +import { + LATEST_PROTOCOL_VERSION, + McpUiInitializeRequestSchema, + McpUiInitializeResultSchema, + McpUiInitializedNotificationSchema, +} from "./types"; + +const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; + +type ConnectedPair = { + app: App; + server: Server; + sentByApp: JSONRPCMessage[]; + appsInitialized: Promise; +}; + +async function connectPair( + app = new App( + { name: "view-app", version: "1.0.0" }, + { tools: { listChanged: true } }, + { autoResize: false }, + ), +): Promise { + const server = new Server( + { name: "inner-mcp-server", version: "0.0.0" }, + { + capabilities: { resources: {}, tools: {} }, + supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], + }, + ); + const [appTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const sentByApp: JSONRPCMessage[] = []; + const send = appTransport.send.bind(appTransport); + appTransport.send = async (message, options) => { + sentByApp.push(message); + await send(message, options); + }; + + let resolveAppsInitialized!: () => void; + const appsInitialized = new Promise((resolve) => { + resolveAppsInitialized = resolve; + }); + + server.setRequestHandler( + "ui/initialize", + { + params: McpUiInitializeRequestSchema.shape.params, + result: McpUiInitializeResultSchema, + }, + (params) => { + expect(params.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); + return { + protocolVersion: LATEST_PROTOCOL_VERSION, + hostCapabilities: { openLinks: {} }, + hostInfo: { name: "apps-host", version: "2.0.0" }, + hostContext: { theme: "dark" as const, locale: "en-US" }, + }; + }, + ); + server.setNotificationHandler( + "ui/notifications/initialized", + { params: McpUiInitializedNotificationSchema.shape.params }, + () => resolveAppsInitialized(), + ); + server.setRequestHandler("tools/call", (request) => ({ + content: [{ type: "text", text: `called ${request.params.name}` }], + })); + server.setRequestHandler("resources/read", (request) => ({ + contents: [{ uri: request.params.uri, text: "resource body" }], + })); + server.setRequestHandler("resources/list", () => ({ + resources: [{ uri: "test://resource", name: "Test resource" }], + })); + server.setRequestHandler("sampling/createMessage", () => ({ + model: "test-model", + role: "assistant", + content: { type: "text", text: "sampled" }, + })); + + await server.connect(serverTransport); + await app.connect(appTransport); + await appsInitialized; + + return { app, server, sentByApp, appsInitialized }; +} + +const connected: Array> = []; + +afterEach(async () => { + await Promise.all( + connected + .splice(0) + .flatMap(({ app, server }) => [app.close(), server.close()]), + ); +}); + +describe("App base MCP SDK v2 Client migration", () => { + it("completes the legacy MCP handshake before the authoritative Apps handshake", async () => { + const pair = await connectPair(); + connected.push(pair); + + const lifecycleMethods = pair.sentByApp + .filter( + (message): message is JSONRPCMessage & { method: string } => + "method" in message, + ) + .map((message) => message.method) + .filter((method) => + [ + "initialize", + "notifications/initialized", + "ui/initialize", + "ui/notifications/initialized", + ].includes(method), + ); + + expect(lifecycleMethods).toEqual([ + "initialize", + "notifications/initialized", + "ui/initialize", + "ui/notifications/initialized", + ]); + expect(pair.app.getNegotiatedProtocolVersion()).toBe( + INNER_MCP_PROTOCOL_VERSION, + ); + }); + + it("uses ui/initialize as the authority for Apps host state", async () => { + const pair = await connectPair(); + connected.push(pair); + + expect(pair.app.getServerVersion()).toEqual({ + name: "inner-mcp-server", + version: "0.0.0", + }); + expect(pair.app.getHostVersion()).toEqual({ + name: "apps-host", + version: "2.0.0", + }); + expect(pair.app.getHostCapabilities()).toEqual({ openLinks: {} }); + expect(pair.app.getHostContext()).toEqual({ + theme: "dark", + locale: "en-US", + }); + }); + + it("preserves registered App tools with the base MCP SDK v2 Client handler context", async () => { + const pair = await connectPair(); + connected.push(pair); + let receivedSignal: AbortSignal | undefined; + + pair.app.registerTool( + "greet", + { description: "Greets a person" }, + async (extra) => { + receivedSignal = extra.mcpReq.signal; + return { content: [{ type: "text", text: "hello" }] }; + }, + ); + + const listed = await pair.server.request({ + method: "tools/list", + params: {}, + }); + const result = await pair.server.request({ + method: "tools/call", + params: { name: "greet", arguments: {} }, + }); + + expect(listed.tools.map((tool) => tool.name)).toEqual(["greet"]); + expect(result.content).toEqual([{ type: "text", text: "hello" }]); + expect(receivedSignal).toBeInstanceOf(AbortSignal); + }); + + it("uses base MCP SDK v2 method-keyed sends for standard MCP requests", async () => { + const pair = await connectPair(); + connected.push(pair); + + const toolResult = await pair.app.callServerTool({ + name: "refresh", + arguments: {}, + }); + const resource = await pair.app.readServerResource({ + uri: "test://resource", + }); + const resources = await pair.app.listServerResources(); + const sample = await pair.app.createSamplingMessage({ + messages: [{ role: "user", content: { type: "text", text: "hello" } }], + maxTokens: 20, + }); + + expect(toolResult.content).toEqual([ + { type: "text", text: "called refresh" }, + ]); + expect(resource.contents[0]).toEqual({ + uri: "test://resource", + text: "resource body", + }); + expect(resources.resources.map(({ uri }) => uri)).toEqual([ + "test://resource", + ]); + expect(sample.content).toEqual({ type: "text", text: "sampled" }); + }); + + it("preserves reconnect behavior after close", async () => { + const first = await connectPair(); + await first.app.close(); + await first.server.close(); + + const second = await connectPair(first.app); + connected.push(second); + + expect(second.app.getNegotiatedProtocolVersion()).toBe( + INNER_MCP_PROTOCOL_VERSION, + ); + expect(second.app.getHostVersion()).toEqual({ + name: "apps-host", + version: "2.0.0", + }); + }); + + it("preserves auto-resize setup after Apps initialization", async () => { + const app = new App( + { name: "view-app", version: "1.0.0" }, + {}, + { autoResize: true }, + ); + let setupCalls = 0; + app.setupSizeChangedNotifications = () => { + setupCalls += 1; + return () => {}; + }; + + const pair = await connectPair(app); + connected.push(pair); + + expect(setupCalls).toBe(1); + }); + + it("preserves strict late-handler timing errors", async () => { + const app = new App( + { name: "view-app", version: "1.0.0" }, + {}, + { autoResize: false, strict: true }, + ); + const pair = await connectPair(app); + connected.push(pair); + + expect(() => { + app.ontoolinput = () => {}; + }).toThrow(/handler registered after connect/); + }); + + it("uses base MCP SDK replacement semantics for request handlers", async () => { + const pair = await connectPair(); + connected.push(pair); + let handledBy = 0; + + pair.app.setRequestHandler("ping", () => { + handledBy = 1; + return {}; + }); + pair.app.setRequestHandler("ping", () => { + handledBy = 2; + return {}; + }); + + await pair.server.request({ method: "ping", params: {} }); + expect(handledBy).toBe(2); + }); + + it("uses base MCP SDK replacement semantics for non-event notifications", async () => { + const pair = await connectPair(); + connected.push(pair); + const calls: number[] = []; + const params = z.object({}); + + pair.app.setNotificationHandler("test/notification", { params }, () => { + calls.push(1); + }); + pair.app.setNotificationHandler("test/notification", { params }, () => { + calls.push(2); + }); + await pair.server.notification({ + method: "test/notification", + params: {}, + }); + + expect(calls).toEqual([2]); + }); +}); diff --git a/src/app.ts b/src/app.ts index c586c3127..3c8f5b79e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,38 +1,27 @@ import { + Client, + type ClientContext, + type ClientOptions, type RequestOptions, - mergeCapabilities, - Protocol, - ProtocolOptions, -} from "@modelcontextprotocol/sdk/shared/protocol.js"; - -import { - CallToolRequest, - CallToolRequestSchema, - CallToolResult, - CallToolResultSchema, - CreateMessageRequest, - CreateMessageResult, - CreateMessageResultSchema, - CreateMessageResultWithTools, - CreateMessageResultWithToolsSchema, - EmptyResultSchema, - Implementation, - ListResourcesRequest, - ListResourcesResult, - ListResourcesResultSchema, - ListToolsRequest, - ListToolsRequestSchema, - ListToolsResult, - LoggingMessageNotification, - PingRequestSchema, - ReadResourceRequest, - ReadResourceResult, - ReadResourceResultSchema, - Tool, - ToolAnnotations, - ToolListChangedNotification, -} from "@modelcontextprotocol/sdk/types.js"; -import { AppNotification, AppRequest, AppResult } from "./types"; + type CallToolRequest, + type CallToolResult, + type CreateMessageRequest, + type CreateMessageResult, + type CreateMessageResultWithTools, + type Implementation, + type ListResourcesRequest, + type ListResourcesResult, + type ListToolsRequest, + type ListToolsResult, + type LoggingMessageNotification, + type ReadResourceRequest, + type ReadResourceResult, + type Tool, + type ToolAnnotations, + type ToolListChangedNotification, + type Transport, +} from "@modelcontextprotocol/client"; +import { EmptyResultSchema } from "@modelcontextprotocol/core"; import { EventDispatcher } from "./events"; export { EventDispatcher } from "./events"; import { PostMessageTransport } from "./message-transport"; @@ -56,6 +45,7 @@ import { McpUiResourceTeardownRequest, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResult, + McpUiResourceTeardownResultSchema, McpUiRequestTeardownNotification, McpUiSizeChangedNotification, McpUiToolCancelledNotification, @@ -69,15 +59,36 @@ import { McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeResultSchema, } from "./types"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { StandardSchemaV1, standardSchemaToJsonSchema, validateStandardSchema, } from "./standard-schema"; -import { z, type ZodLiteral, type ZodObject } from "zod/v4"; - -type MethodSchema = ZodObject<{ method: ZodLiteral }>; +import { z, type ZodLiteral, type ZodObject, type ZodType } from "zod/v4"; + +type MethodSchema = ZodObject<{ + method: ZodLiteral; + params: ZodType; +}>; + +const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; + +function mergeAppCapabilities( + base: McpUiAppCapabilities, + additional: McpUiAppCapabilities, +): McpUiAppCapabilities { + const result = { ...base, ...additional }; + if (additional.experimental !== undefined) { + result.experimental = { + ...base.experimental, + ...additional.experimental, + }; + } + if (additional.tools !== undefined) { + result.tools = { ...base.tools, ...additional.tools }; + } + return result; +} export type { StandardSchemaV1, @@ -167,7 +178,10 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; * * @see `ProtocolOptions` from @modelcontextprotocol/sdk for inherited options */ -export type AppOptions = ProtocolOptions & { +export type AppOptions = Omit< + ClientOptions, + "capabilities" | "supportedProtocolVersions" | "versionNegotiation" +> & { /** * Automatically report size changes to the host using `ResizeObserver`. * @@ -207,9 +221,7 @@ export type AppOptions = ProtocolOptions & { allowUnsafeEval?: boolean; }; -type RequestHandlerExtra = Parameters< - Parameters[1] ->[1]; +type RequestHandlerExtra = ClientContext; /** * Result of an app-registered tool callback. When `Out` is provided, @@ -343,38 +355,15 @@ export type AppEventMap = { * await app.connect(); * ``` */ -export class App extends Protocol { +export class App extends Client { private _hostCapabilities?: McpUiHostCapabilities; private _hostInfo?: Implementation; private _hostContext?: McpUiHostContext; private _registeredTools: { [name: string]: RegisteredAppTool } = {}; private _initializedSent = false; - private readonly _registeredMethods = new Set(); private readonly _registeredEvents = new Set(); private readonly _events = new EventDispatcher(); - // ProtocolWithEvents previously owned these registration guards. Keep them - // on App to preserve the existing double-registration behavior while event - // fan-out moves to protocol-independent composition. Arrow-function fields - // ensure Protocol's constructor uses its own methods before the guards init. - override setRequestHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setRequestHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setRequestHandler"); - super.setRequestHandler(schema, handler); - }; - - override setNotificationHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setNotificationHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setNotificationHandler"); - super.setNotificationHandler(schema, handler); - }; - /** * Warn if a host-bound method is called before {@link connect `connect`} has * completed the `ui/initialize` → `ui/notifications/initialized` handshake. @@ -479,13 +468,17 @@ export class App extends Protocol { if (this._registeredEvents.has(event)) return; const schema = this.eventSchemas[event]; if (!schema) throw new Error(`Unknown event: ${String(event)}`); - this.setNotificationHandler(schema, (notification) => { - const params = (notification as { params: AppEventMap[K] }).params; - if (event === "hostcontextchanged") { - this._hostContext = { ...this._hostContext, ...params }; - } - this._events.dispatch(event, params); - }); + this.setNotificationHandler( + schema.shape.method.value, + { params: schema.shape.params }, + (params) => { + const eventParams = params as AppEventMap[K]; + if (event === "hostcontextchanged") { + this._hostContext = { ...this._hostContext, ...eventParams }; + } + this._events.dispatch(event, eventParams); + }, + ); this._registeredEvents.add(event); } @@ -508,33 +501,11 @@ export class App extends Protocol { } } - protected replaceRequestHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setRequestHandler"] = (schema, handler) => { - const method = (schema as MethodSchema).shape.method.value; - this._registeredMethods.add(method); - super.setRequestHandler(schema, handler); - }; - - private _assertMethodNotRegistered(schema: unknown, via: string): void { - const method = (schema as MethodSchema).shape.method.value; - if (this._registeredMethods.has(method)) { - throw new Error( - `Handler for "${method}" already registered (via ${via}). ` + - `Use addEventListener() to attach multiple listeners, ` + - `or the on* setter for replace semantics.`, - ); - } - this._registeredMethods.add(method); - } - /** * Create a new MCP App instance. * * @param _appInfo - App identification (name and version) - * @param _capabilities - Features and capabilities this app provides + * @param _appCapabilities - Features and capabilities this app provides * @param options - Configuration options including `autoResize` behavior * * @example @@ -548,16 +519,29 @@ export class App extends Protocol { */ constructor( private _appInfo: Implementation, - private _capabilities: McpUiAppCapabilities = {}, + private _appCapabilities: McpUiAppCapabilities = {}, private options: AppOptions = { autoResize: true }, ) { - super(options); + // TEMPORARY INNER-CHANNEL NEGOTIATION: + // App and AppBridge use the public v2 Client/Server roles, so the iframe + // channel first completes the SDK's standard legacy MCP initialization. + // ui/initialize remains authoritative for Apps capabilities, identity, and + // host context, and ui/notifications/initialized remains the View-ready gate. + // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only + // after the MCP Apps negotiation specification is resolved: + // https://github.com/modelcontextprotocol/ext-apps/issues/708 + super(_appInfo, { + ...options, + capabilities: {}, + versionNegotiation: { mode: "legacy" }, + supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], + }); if (!options.allowUnsafeEval) { z.config({ jitless: true }); } - this.setRequestHandler(PingRequestSchema, (request) => { + this.setRequestHandler("ping", (request) => { console.log("Received ping:", request.params); return {}; }); @@ -567,13 +551,16 @@ export class App extends Protocol { this.setEventHandler("hostcontextchanged", undefined); } - private registerCapabilities(capabilities: McpUiAppCapabilities): void { + private registerAppCapabilities(capabilities: McpUiAppCapabilities): void { if (this.transport) { throw new Error( "Cannot register capabilities after transport is established", ); } - this._capabilities = mergeCapabilities(this._capabilities, capabilities); + this._appCapabilities = mergeAppCapabilities( + this._appCapabilities, + capabilities, + ); } registerTool< @@ -596,7 +583,7 @@ export class App extends Protocol { } const app = this; const notify = () => { - if (app._initializedSent && app._capabilities.tools?.listChanged) { + if (app._initializedSent && app._appCapabilities.tools?.listChanged) { void app.sendToolListChanged(); } }; @@ -668,8 +655,8 @@ export class App extends Protocol { // to declare { tools: {} } in the constructor just to use registerTool. // Only do this pre-connect; post-connect the capability was already // advertised (or wasn't) and can't change. - if (!this._capabilities.tools && !this.transport) { - this.registerCapabilities({ tools: { listChanged: true } }); + if (!this._appCapabilities.tools && !this.transport) { + this.registerAppCapabilities({ tools: { listChanged: true } }); } this.ensureToolHandlersInitialized(); @@ -1081,11 +1068,15 @@ export class App extends Protocol { ) { this.warnIfRequestHandlerReplaced("onteardown", this._onteardown, callback); this._onteardown = callback; - this.replaceRequestHandler( - McpUiResourceTeardownRequestSchema, - (request, extra) => { + this.setRequestHandler( + "ui/resource-teardown", + { + params: McpUiResourceTeardownRequestSchema.shape.params, + result: McpUiResourceTeardownResultSchema, + }, + (params, extra) => { if (!this._onteardown) throw new Error("No onteardown handler set"); - return this._onteardown(request.params, extra); + return this._onteardown(params, extra); }, ); } @@ -1135,7 +1126,7 @@ export class App extends Protocol { ) { this.warnIfRequestHandlerReplaced("oncalltool", this._oncalltool, callback); this._oncalltool = callback; - this.replaceRequestHandler(CallToolRequestSchema, (request, extra) => { + this.setRequestHandler("tools/call", (request, extra) => { if (!this._oncalltool) throw new Error("No oncalltool handler set"); return this._oncalltool(request.params, extra); }); @@ -1206,7 +1197,7 @@ export class App extends Protocol { callback, ); this._onlisttools = callback; - this.replaceRequestHandler(ListToolsRequestSchema, (request, extra) => { + this.setRequestHandler("tools/list", (request, extra) => { if (!this._onlisttools) throw new Error("No onlisttools handler set"); return this._onlisttools(request.params, extra); }); @@ -1216,7 +1207,7 @@ export class App extends Protocol { * Verify that the host supports the capability required for the given request method. * @internal */ - assertCapabilityForMethod(method: AppRequest["method"]): void { + protected override assertCapabilityForMethod(method: string): void { switch (method) { case "sampling/createMessage": if (!this._hostCapabilities?.sampling) { @@ -1232,11 +1223,11 @@ export class App extends Protocol { * Verify that the app declared the capability required for the given request method. * @internal */ - assertRequestHandlerCapability(method: AppRequest["method"]): void { + protected override assertRequestHandlerCapability(method: string): void { switch (method) { case "tools/call": case "tools/list": - if (!this._capabilities.tools) { + if (!this._appCapabilities.tools) { throw new Error( `Client does not support tool capability (required for ${method})`, ); @@ -1254,26 +1245,10 @@ export class App extends Protocol { * Verify that the app supports the capability required for the given notification method. * @internal */ - assertNotificationCapability(_method: AppNotification["method"]): void { + protected override assertNotificationCapability(_method: string): void { // TODO } - /** - * Verify that task creation is supported for the given request method. - * @internal - */ - protected assertTaskCapability(_method: string): void { - throw new Error("Tasks are not supported in MCP Apps"); - } - - /** - * Verify that task handler is supported for the given method. - * @internal - */ - protected assertTaskHandlerCapability(_method: string): void { - throw new Error("Task handlers are not supported in MCP Apps"); - } - /** * Call a tool on the originating MCP server (proxied through the host). * @@ -1322,7 +1297,6 @@ export class App extends Protocol { } return await this.request( { method: "tools/call", params }, - CallToolResultSchema, { // Hosts may interpose long-running or user-interactive steps before the // tool result arrives. Opting in here lets a host heartbeat keep the @@ -1379,11 +1353,7 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("readServerResource"); - return await this.request( - { method: "resources/read", params }, - ReadResourceResultSchema, - options, - ); + return await this.request({ method: "resources/read", params }, options); } /** @@ -1427,11 +1397,7 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("listServerResources"); - return await this.request( - { method: "resources/list", params }, - ListResourcesResultSchema, - options, - ); + return await this.request({ method: "resources/list", params }, options); } /** @@ -1508,12 +1474,8 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("createSamplingMessage"); - const resultSchema = params.tools - ? CreateMessageResultWithToolsSchema - : CreateMessageResultSchema; return await this.request( { method: "sampling/createMessage", params }, - resultSchema, options, ); } @@ -2030,14 +1992,23 @@ export class App extends Protocol { ); } this._initializedSent = false; - await super.connect(transport); + // TEMPORARY INNER-CHANNEL NEGOTIATION: + // App and AppBridge use the public v2 Client/Server roles, so the iframe + // channel first completes the SDK's standard legacy MCP initialization. + // ui/initialize remains authoritative for Apps capabilities, identity, and + // host context, and ui/notifications/initialized remains the View-ready gate. + // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only + // after the MCP Apps negotiation specification is resolved: + // https://github.com/modelcontextprotocol/ext-apps/issues/708 try { + await super.connect(transport); + const result = await this.request( { method: "ui/initialize", params: { - appCapabilities: this._capabilities, + appCapabilities: this._appCapabilities, appInfo: this._appInfo, protocolVersion: LATEST_PROTOCOL_VERSION, }, From d19a1fb83ade586d6e2eb46535e7d84a7f7468a8 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 15:34:33 -0700 Subject: [PATCH 4/9] refactor: migrate AppBridge to SDK v2 server --- src/app-bridge.test.ts | 336 ++++++++++++++++++++++---- src/app-bridge.ts | 527 ++++++++++++++++++++--------------------- 2 files changed, 546 insertions(+), 317 deletions(-) diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 8f432b428..81e072fd6 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -1,14 +1,6 @@ import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import type { ServerCapabilities } from "@modelcontextprotocol/sdk/types.js"; -import { - EmptyResultSchema, - ListPromptsResultSchema, - ListResourcesResultSchema, - ListResourceTemplatesResultSchema, - ReadResourceResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { Server, type ServerCapabilities } from "@modelcontextprotocol/server"; import { z } from "zod/v4"; import { App } from "./app"; @@ -19,6 +11,7 @@ import { getToolUiResourceUri, isToolVisibilityModelOnly, isToolVisibilityAppOnly, + McpUiOpenLinkResultSchema, type McpUiHostCapabilities, } from "./app-bridge"; @@ -31,11 +24,18 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); */ function createMockClient( serverCapabilities: ServerCapabilities = {}, -): Pick { +): Pick< + Client, + | "getServerCapabilities" + | "request" + | "notification" + | "setNotificationHandler" +> { return { getServerCapabilities: () => serverCapabilities, request: async () => ({}) as never, notification: async () => {}, + setNotificationHandler: () => {}, }; } @@ -82,6 +82,58 @@ describe("App <-> AppBridge integration", () => { expect(initializedFired).toBe(true); }); + it("keeps standard initialization separate from the Apps-ready gate", async () => { + const methods: string[] = []; + let releaseAppsInitialized!: () => void; + let reachedAppsInitialized!: () => void; + const appsInitializedGate = new Promise((resolve) => { + releaseAppsInitialized = resolve; + }); + const reachedGate = new Promise((resolve) => { + reachedAppsInitialized = resolve; + }); + const originalSend = appTransport.send.bind(appTransport); + appTransport.send = async (message, options) => { + if ("method" in message) methods.push(message.method); + if ( + "method" in message && + message.method === "ui/notifications/initialized" + ) { + reachedAppsInitialized(); + await appsInitializedGate; + } + return originalSend(message, options); + }; + + let singularCalls = 0; + let listenerCalls = 0; + bridge.oninitialized = () => singularCalls++; + bridge.addEventListener("initialized", () => listenerCalls++); + + await bridge.connect(bridgeTransport); + const connecting = app.connect(appTransport); + await reachedGate; + + expect(methods).toEqual([ + "initialize", + "notifications/initialized", + "ui/initialize", + "ui/notifications/initialized", + ]); + expect(app.getNegotiatedProtocolVersion()).toBe("2025-11-25"); + expect(bridge.getNegotiatedProtocolVersion()).toBe("2025-11-25"); + expect(bridge.getClientVersion()).toEqual(testAppInfo); + expect(bridge.getAppVersion()).toEqual(testAppInfo); + expect(singularCalls).toBe(0); + expect(listenerCalls).toBe(0); + + releaseAppsInitialized(); + await connecting; + + expect(singularCalls).toBe(1); + expect(listenerCalls).toBe(1); + }); + it("App receives host info and capabilities after connect", async () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -91,6 +143,8 @@ describe("App <-> AppBridge integration", () => { const hostCaps = app.getHostCapabilities(); expect(hostCaps).toEqual(testHostCapabilities); + expect(bridge.getHostCapabilities()).toEqual(testHostCapabilities); + expect(bridge.getCapabilities()).toEqual({}); }); it("Bridge receives app info and capabilities after initialization", async () => { @@ -611,6 +665,30 @@ describe("App <-> AppBridge integration", () => { expect(result.isError).toBe(true); }); + + it("rejects invalid params for a custom ui request", async () => { + bridge.onopenlink = async () => ({}); + await app.connect(appTransport); + + await expect( + app.request( + { + method: "ui/open-link", + params: { url: 42 }, + }, + McpUiOpenLinkResultSchema, + ), + ).rejects.toThrow(); + }); + + it("rejects invalid results from a custom ui handler", async () => { + bridge.onopenlink = async () => ({ isError: "yes" }) as never; + await app.connect(appTransport); + + await expect( + app.openLink({ url: "https://example.com" }), + ).rejects.toThrow(); + }); }); describe("deprecated method aliases", () => { @@ -680,10 +758,7 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); // Bridge can send ping via the protocol's request method - const result = await bridge.request( - { method: "ping", params: {} }, - EmptyResultSchema, - ); + const result = await bridge.request({ method: "ping", params: {} }); expect(result).toEqual({}); }); @@ -798,7 +873,7 @@ describe("App <-> AppBridge integration", () => { }); await bridge.callTool({ name: "noargs", arguments: {} }); expect(receivedExtra).toBeDefined(); - expect(receivedExtra.signal).toBeInstanceOf(AbortSignal); + expect(receivedExtra.mcpReq.signal).toBeInstanceOf(AbortSignal); }); it("isError result skips output schema validation", async () => { @@ -2180,10 +2255,10 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); // App sends resources/list request via the protocol's request method - const result = await app.request( - { method: "resources/list", params: requestParams }, - ListResourcesResultSchema, - ); + const result = await app.request({ + method: "resources/list", + params: requestParams, + }); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2224,10 +2299,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request( - { method: "resources/read", params: requestParams }, - ReadResourceResultSchema, - ); + const result = await app.request({ + method: "resources/read", + params: requestParams, + }); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2275,10 +2350,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request( - { method: "resources/templates/list", params: requestParams }, - ListResourceTemplatesResultSchema, - ); + const result = await app.request({ + method: "resources/templates/list", + params: requestParams, + }); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2298,10 +2373,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request( - { method: "prompts/list", params: requestParams }, - ListPromptsResultSchema, - ); + const result = await app.request({ + method: "prompts/list", + params: requestParams, + }); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2310,6 +2385,7 @@ describe("App <-> AppBridge integration", () => { it("sendToolListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; + bridge.oncalltool = async () => ({ content: [] }); app.setNotificationHandler( "notifications/tools/list_changed", (notification) => { @@ -2328,6 +2404,7 @@ describe("App <-> AppBridge integration", () => { it("sendResourceListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; + bridge.onlistresources = async () => ({ resources: [] }); app.setNotificationHandler( "notifications/resources/list_changed", (notification) => { @@ -2346,6 +2423,7 @@ describe("App <-> AppBridge integration", () => { it("sendPromptListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; + bridge.onlistprompts = async () => ({ prompts: [] }); app.setNotificationHandler( "notifications/prompts/list_changed", (notification) => { @@ -2364,6 +2442,184 @@ describe("App <-> AppBridge integration", () => { }); }); +describe("AppBridge outer v2 Client proxy", () => { + let outerClient: Client; + let outerServer: Server; + let outerClientTransport: InMemoryTransport; + let outerServerTransport: InMemoryTransport; + let outerInitializeCount: number; + + beforeEach(async () => { + [outerClientTransport, outerServerTransport] = + InMemoryTransport.createLinkedPair(); + outerInitializeCount = 0; + const originalSend = outerClientTransport.send.bind(outerClientTransport); + outerClientTransport.send = async (message, options) => { + if ("method" in message && message.method === "initialize") { + outerInitializeCount++; + } + return originalSend(message, options); + }; + + outerServer = new Server( + { name: "ActualServer", version: "1.0.0" }, + { + capabilities: { + tools: { listChanged: true }, + resources: { listChanged: true }, + prompts: { listChanged: true }, + }, + supportedProtocolVersions: ["2025-11-25"], + }, + ); + outerServer.setRequestHandler("tools/call", async (request) => ({ + content: [ + { + type: "text", + text: `outer:${request.params.name}`, + }, + ], + })); + + outerClient = new Client( + { name: "HostOuterClient", version: "1.0.0" }, + { + capabilities: {}, + versionNegotiation: { mode: "legacy" }, + supportedProtocolVersions: ["2025-11-25"], + }, + ); + await outerServer.connect(outerServerTransport); + await outerClient.connect(outerClientTransport); + }); + + afterEach(async () => { + await outerClient.close().catch(() => {}); + await outerServer.close().catch(() => {}); + }); + + it("proxies through the outer Client without reconnecting it", async () => { + const bridge = new AppBridge( + outerClient, + testHostInfo, + testHostCapabilities, + ); + const app = new App(testAppInfo, {}, { autoResize: false }); + let [appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair(); + + await bridge.connect(bridgeTransport); + await app.connect(appTransport); + expect( + await app.callServerTool({ name: "first", arguments: {} }), + ).toMatchObject({ + content: [{ type: "text", text: "outer:first" }], + }); + expect(outerInitializeCount).toBe(1); + + await app.close(); + await bridge.close(); + [appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair(); + await bridge.connect(bridgeTransport); + await app.connect(appTransport); + + expect( + await app.callServerTool({ name: "second", arguments: {} }), + ).toMatchObject({ + content: [{ type: "text", text: "outer:second" }], + }); + expect(outerInitializeCount).toBe(1); + expect(outerClient.getServerVersion()).toMatchObject({ + name: "ActualServer", + version: "1.0.0", + }); + + await app.close(); + await bridge.close(); + }); + + it("propagates View cancellation to the actual outer Server", async () => { + let started!: () => void; + let aborted!: () => void; + const startedPromise = new Promise((resolve) => { + started = resolve; + }); + const abortedPromise = new Promise((resolve) => { + aborted = resolve; + }); + outerServer.setRequestHandler("tools/call", async (_request, context) => { + started(); + await new Promise((resolve) => { + context.mcpReq.signal.addEventListener( + "abort", + () => { + aborted(); + resolve(); + }, + { once: true }, + ); + }); + return { content: [] }; + }); + + const bridge = new AppBridge( + outerClient, + testHostInfo, + testHostCapabilities, + ); + const app = new App(testAppInfo, {}, { autoResize: false }); + const [appTransport, bridgeTransport] = + InMemoryTransport.createLinkedPair(); + await bridge.connect(bridgeTransport); + await app.connect(appTransport); + + const controller = new AbortController(); + const result = app.callServerTool( + { name: "cancel-me", arguments: {} }, + { signal: controller.signal }, + ); + await startedPromise; + controller.abort("test cancellation"); + await abortedPromise; + await expect(result).rejects.toThrow(); + + await app.close(); + await bridge.close(); + }); + + it("forwards outer list-changed notifications to the App", async () => { + const bridge = new AppBridge( + outerClient, + testHostInfo, + testHostCapabilities, + ); + const app = new App(testAppInfo, {}, { autoResize: false }); + const [appTransport, bridgeTransport] = + InMemoryTransport.createLinkedPair(); + const received: string[] = []; + app.setNotificationHandler("notifications/tools/list_changed", () => { + received.push("tools"); + }); + app.setNotificationHandler("notifications/resources/list_changed", () => { + received.push("resources"); + }); + app.setNotificationHandler("notifications/prompts/list_changed", () => { + received.push("prompts"); + }); + + await bridge.connect(bridgeTransport); + await app.connect(appTransport); + await outerServer.sendToolListChanged(); + await outerServer.sendResourceListChanged(); + await outerServer.sendPromptListChanged(); + await flush(); + + expect(received).toEqual(["tools", "resources", "prompts"]); + + await app.close(); + await bridge.close(); + }); +}); + describe("getToolUiResourceUri", () => { describe("new nested format (_meta.ui.resourceUri)", () => { it("extracts resourceUri from _meta.ui.resourceUri", () => { @@ -2758,24 +3014,16 @@ describe("isToolVisibilityAppOnly", () => { expect(app.onteardown).toBe(handler); }); - it("direct setRequestHandler throws when called twice", () => { + it("direct setRequestHandler uses base SDK replacement semantics", () => { const bridge2 = new AppBridge( createMockClient() as Client, testHostInfo, testHostCapabilities, ); - bridge2.setRequestHandler( - // @ts-expect-error — exercising throw path with raw schema - { shape: { method: { value: "test/method" } } }, - () => ({}), - ); + bridge2.setRequestHandler("ping", () => ({})); expect(() => { - bridge2.setRequestHandler( - // @ts-expect-error — exercising throw path with raw schema - { shape: { method: { value: "test/method" } } }, - () => ({}), - ); - }).toThrow(/already registered/); + bridge2.setRequestHandler("ping", () => ({})); + }).not.toThrow(); }); }); }); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 18d76718f..c65510d39 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -1,61 +1,51 @@ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { type Client } from "@modelcontextprotocol/client"; import { - CallToolRequest, - CallToolRequestSchema, - CallToolResult, - CallToolResultSchema, - CreateMessageRequest, - CreateMessageRequestSchema, - CreateMessageResult, - CreateMessageResultWithTools, - EmptyResult, - Implementation, - ListPromptsRequest, - ListPromptsRequestSchema, - ListPromptsResult, - ListPromptsResultSchema, - ListResourcesRequest, - ListResourcesRequestSchema, - ListResourcesResult, - ListResourcesResultSchema, - ListResourceTemplatesRequest, - ListResourceTemplatesRequestSchema, - ListResourceTemplatesResult, - ListResourceTemplatesResultSchema, - ListToolsRequest, - ListToolsRequestSchema, - ListToolsResultSchema, - LoggingMessageNotification, - LoggingMessageNotificationSchema, - PingRequest, - PingRequestSchema, - PromptListChangedNotification, - PromptListChangedNotificationSchema, - ReadResourceRequest, - ReadResourceRequestSchema, - ReadResourceResult, - ReadResourceResultSchema, - ResourceListChangedNotification, - ResourceListChangedNotificationSchema, - Tool, - ToolListChangedNotification, - ToolListChangedNotificationSchema, -} from "@modelcontextprotocol/sdk/types.js"; + Server, + type CallToolRequest, + type CallToolResult, + type CreateMessageRequest, + type CreateMessageResult, + type CreateMessageResultWithTools, + type EmptyResult, + type Implementation, + type JSONRPCRequest, + type ListPromptsRequest, + type ListPromptsResult, + type ListResourcesRequest, + type ListResourcesResult, + type ListResourceTemplatesRequest, + type ListResourceTemplatesResult, + type ListToolsRequest, + type LoggingMessageNotification, + type PingRequest, + type PromptListChangedNotification, + type ReadResourceRequest, + type ReadResourceResult, + type RequestOptions, + type ResourceListChangedNotification, + type Result, + type ServerCapabilities, + type ServerContext, + type ServerOptions, + type Tool, + type ToolListChangedNotification, + type Transport, +} from "@modelcontextprotocol/server"; import { - Protocol, - ProtocolOptions, - RequestOptions, -} from "@modelcontextprotocol/sdk/shared/protocol.js"; + EmptyResultSchema, + LoggingMessageNotificationSchema, +} from "@modelcontextprotocol/core"; import { EventDispatcher } from "./events"; -import type { ZodLiteral, ZodObject } from "zod/v4"; +import type { ZodLiteral, ZodObject, ZodType } from "zod/v4"; -type MethodSchema = ZodObject<{ method: ZodLiteral }>; +type MethodSchema = ZodObject<{ + method: ZodLiteral; + params: ZodType; +}>; + +const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; import { - type AppNotification, - type AppRequest, - type AppResult, type McpUiSandboxResourceReadyNotification, type McpUiSizeChangedNotification, type McpUiToolCancelledNotification, @@ -74,15 +64,19 @@ import { McpUiInitializeRequest, McpUiInitializeRequestSchema, McpUiInitializeResult, + McpUiInitializeResultSchema, McpUiMessageRequest, McpUiMessageRequestSchema, McpUiMessageResult, + McpUiMessageResultSchema, McpUiOpenLinkRequest, McpUiOpenLinkRequestSchema, McpUiOpenLinkResult, + McpUiOpenLinkResultSchema, McpUiDownloadFileRequest, McpUiDownloadFileRequestSchema, McpUiDownloadFileResult, + McpUiDownloadFileResultSchema, McpUiResourceTeardownRequest, McpUiResourceTeardownResultSchema, McpUiRequestTeardownNotification, @@ -93,6 +87,7 @@ import { McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeRequestSchema, McpUiRequestDisplayModeResult, + McpUiRequestDisplayModeResultSchema, McpUiResourcePermissions, McpUiToolMeta, } from "./types"; @@ -101,6 +96,20 @@ export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app"; import { RESOURCE_URI_META_KEY } from "./app"; export { PostMessageTransport } from "./message-transport"; +function proxiedInnerCapabilities( + capabilities: ServerCapabilities, +): ServerCapabilities { + return { + ...(capabilities.tools && { tools: capabilities.tools }), + ...(capabilities.resources && { + resources: { + listChanged: capabilities.resources.listChanged, + }, + }), + ...(capabilities.prompts && { prompts: capabilities.prompts }), + }; +} + /** * Extract UI resource URI from tool metadata. * @@ -206,10 +215,13 @@ export function buildAllowAttribute( * * @property hostContext - Optional initial host context to provide to the view * - * @see `ProtocolOptions` from @modelcontextprotocol/sdk for available options + * @see `ServerOptions` from @modelcontextprotocol/server for available options * @see {@link McpUiHostContext `McpUiHostContext`} for the hostContext structure */ -export type HostOptions = ProtocolOptions & { +export type HostOptions = Omit< + ServerOptions, + "capabilities" | "supportedProtocolVersions" +> & { hostContext?: McpUiHostContext; }; @@ -224,15 +236,12 @@ export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION]; /** * Extra metadata passed to request handlers. * - * This type represents the additional context provided by the `Protocol` class - * when handling requests, including abort signals and session information. - * It is extracted from the MCP SDK's request handler signature. + * This type represents the additional context provided by the base MCP SDK + * `Server` when handling requests. * * @internal */ -type RequestHandlerExtra = Parameters< - Parameters[1] ->[1]; +type RequestHandlerExtra = ServerContext; /** * Maps DOM-style event names to their notification `params` types. @@ -306,66 +315,53 @@ export type AppBridgeEventMap = { * await bridge.connect(transport); * ``` */ -export class AppBridge extends Protocol { +export class AppBridge extends Server { private _appCapabilities?: McpUiAppCapabilities; private _hostContext: McpUiHostContext = {}; private _appInfo?: Implementation; private _initializedReceived = false; - private readonly _registeredMethods = new Set(); private readonly _registeredEvents = new Set(); private readonly _events = new EventDispatcher(); - // ProtocolWithEvents previously owned these registration guards. Keep them - // on AppBridge to preserve the existing double-registration behavior while - // event fan-out moves to protocol-independent composition. Arrow-function - // fields let Protocol's constructor run before the guards initialize. - override setRequestHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setRequestHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setRequestHandler"); - super.setRequestHandler(schema, handler); - }; - - override setNotificationHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setNotificationHandler"] = (schema, handler) => { - this._assertMethodNotRegistered(schema, "setNotificationHandler"); - super.setNotificationHandler(schema, handler); - }; - /** - * Wrap every handler registered via `replaceRequestHandler` with a check - * that the View has sent `ui/notifications/initialized`. Warns (never - * throws) so lenient hosts keep working while still surfacing the - * misordering that leaves strict hosts with a permanently hidden iframe. - * `ui/initialize` and `ping` use `setRequestHandler` directly and are - * intentionally exempt. + * The base MCP SDK calls this hook for every standard and custom request + * handler registration. AppBridge uses it to preserve the existing warning + * when a View calls host methods before `ui/notifications/initialized`, + * without adding overload-forwarding adapters around `setRequestHandler`. + * Chaining through `super._wrapHandler()` retains the base `Server`'s own + * role-specific behavior, including tool-result validation. The two + * initialization methods and `ping` are exempt because they are valid before + * the Apps-ready gate. This wraps registered callbacks, not JSON-RPC dispatch. * * @see {@link https://github.com/anthropics/claude-ai-mcp/issues/149 claude-ai-mcp#149} */ - protected replaceRequestHandler: Protocol< - AppRequest, - AppNotification, - AppResult - >["setRequestHandler"] = (schema, handler) => { - const method = (schema as MethodSchema).shape.method.value; - this._registeredMethods.add(method); - super.setRequestHandler(schema, (request, extra) => { + protected override _wrapHandler( + method: string, + handler: ( + request: JSONRPCRequest, + context: ServerContext, + ) => Promise, + ): (request: JSONRPCRequest, context: ServerContext) => Promise { + const wrapped = super._wrapHandler(method, handler); + if ( + method === "initialize" || + method === "ui/initialize" || + method === "ping" + ) { + return wrapped; + } + return async (request, context) => { if (!this._initializedReceived) { console.warn( - `[ext-apps] AppBridge received '${request.method}' before ` + + `[ext-apps] AppBridge received '${method}' before ` + `ui/notifications/initialized. The View is calling host ` + `methods before completing the handshake; it should await ` + `app.connect() first.`, ); } - return handler(request, extra); - }); - }; + return wrapped(request, context); + }; + } private readonly eventSchemas: { [K in keyof AppBridgeEventMap]: MethodSchema; @@ -406,17 +402,27 @@ export class AppBridge extends Protocol this._events.removeEventListener(event, handler); } - private _ensureEventSlot( - event: K, - ): void { + private _ensureEventSlot(event: K): void { if (this._registeredEvents.has(event)) return; const schema = this.eventSchemas[event]; if (!schema) throw new Error(`Unknown event: ${String(event)}`); - this.setNotificationHandler(schema, (notification) => { - const params = (notification as { params: AppBridgeEventMap[K] }).params; + + const dispatch = (params: AppBridgeEventMap[K]) => { if (event === "initialized") this._initializedReceived = true; this._events.dispatch(event, params); - }); + }; + + if (event === "loggingmessage") { + this.setNotificationHandler("notifications/message", (notification) => { + dispatch(notification.params as AppBridgeEventMap[K]); + }); + } else { + this.setNotificationHandler( + schema.shape.method.value, + { params: schema.shape.params }, + (params) => dispatch(params as AppBridgeEventMap[K]), + ); + } this._registeredEvents.add(event); } @@ -433,16 +439,12 @@ export class AppBridge extends Protocol } } - private _assertMethodNotRegistered(schema: unknown, via: string): void { - const method = (schema as MethodSchema).shape.method.value; - if (this._registeredMethods.has(method)) { - throw new Error( - `Handler for "${method}" already registered (via ${via}). ` + - `Use addEventListener() to attach multiple listeners, ` + - `or the on* setter for replace semantics.`, - ); - } - this._registeredMethods.add(method); + private ensureInnerCapabilities(capabilities: ServerCapabilities): void { + const current = super.getCapabilities(); + const missing = Object.keys(capabilities).some( + (key) => current[key as keyof ServerCapabilities] === undefined, + ); + if (missing) this.registerCapabilities(capabilities); } /** @@ -453,8 +455,8 @@ export class AppBridge extends Protocol * between the View and the server. When `null`, you must register handlers * manually using the {@link oncalltool `oncalltool`}, {@link onlistresources `onlistresources`}, etc. setters. * @param _hostInfo - Host application identification (name and version) - * @param _capabilities - Features and capabilities the host supports - * @param options - Configuration options (inherited from Protocol) + * @param _hostCapabilities - Features and capabilities the host supports + * @param options - Configuration options (inherited from Server) * * @example With MCP client (automatic forwarding) * ```ts source="./app-bridge.examples.ts#AppBridge_constructor_withMcpClient" @@ -481,29 +483,61 @@ export class AppBridge extends Protocol constructor( private _client: Client | null, private _hostInfo: Implementation, - private _capabilities: McpUiHostCapabilities, + private _hostCapabilities: McpUiHostCapabilities, options?: HostOptions, ) { - super(options); + // TEMPORARY INNER-CHANNEL NEGOTIATION: + // App and AppBridge use the public v2 Client/Server roles, so the iframe + // channel first completes the SDK's standard legacy MCP initialization. + // ui/initialize remains authoritative for Apps capabilities, identity, and + // host context, and ui/notifications/initialized remains the View-ready gate. + // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only + // after the MCP Apps negotiation specification is resolved: + // https://github.com/modelcontextprotocol/ext-apps/issues/708 + super(_hostInfo, { + ...options, + capabilities: {}, + supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], + }); + + // The base Server's standard initialized callback is MCP-ready state. The + // public AppBridge callback remains exclusively the Apps View-ready gate. + super.setNotificationHandler("notifications/initialized", () => {}); + Object.defineProperty(this, "oninitialized", { + configurable: true, + enumerable: true, + get: () => this.getEventHandler("initialized"), + set: (callback: AppBridge["oninitialized"]) => + this.setEventHandler("initialized", callback), + }); this._ensureEventSlot("initialized"); this._hostContext = options?.hostContext || {}; - this.setRequestHandler(McpUiInitializeRequestSchema, (request) => - this._oninitialize(request), + this.setRequestHandler( + "ui/initialize", + { + params: McpUiInitializeRequestSchema.shape.params, + result: McpUiInitializeResultSchema, + }, + (params) => this._onAppsInitialize(params), ); - this.setRequestHandler(PingRequestSchema, (request, extra) => { + this.setRequestHandler("ping", (request, extra) => { this.onping?.(request.params, extra); return {}; }); // Default handler for requestDisplayMode - returns current mode from host context. // Hosts can override this by setting bridge.onrequestdisplaymode = ... - this.replaceRequestHandler( - McpUiRequestDisplayModeRequestSchema, - (request) => { + this.setRequestHandler( + "ui/request-display-mode", + { + params: McpUiRequestDisplayModeRequestSchema.shape.params, + result: McpUiRequestDisplayModeResultSchema, + }, + () => { const currentMode = this._hostContext.displayMode ?? "inline"; return { mode: currentMode }; }, @@ -680,18 +714,9 @@ export class AppBridge extends Protocol * @see {@link sendToolInput `sendToolInput`} for sending tool arguments to the View * @deprecated Use {@link addEventListener `addEventListener("initialized", handler)`} instead — it composes with other listeners and supports cleanup via {@link removeEventListener `removeEventListener`}. */ - get oninitialized(): - | ((params: McpUiInitializedNotification["params"]) => void) - | undefined { - return this.getEventHandler("initialized"); - } - set oninitialized( - callback: - | ((params: McpUiInitializedNotification["params"]) => void) - | undefined, - ) { - this.setEventHandler("initialized", callback); - } + override oninitialized?: ( + params?: McpUiInitializedNotification["params"], + ) => void; /** * Register a handler for message requests from the view. @@ -744,11 +769,15 @@ export class AppBridge extends Protocol ) { this.warnIfRequestHandlerReplaced("onmessage", this._onmessage, callback); this._onmessage = callback; - this.replaceRequestHandler( - McpUiMessageRequestSchema, - async (request, extra) => { + this.setRequestHandler( + "ui/message", + { + params: McpUiMessageRequestSchema.shape.params, + result: McpUiMessageResultSchema, + }, + async (params, extra) => { if (!this._onmessage) throw new Error("No onmessage handler set"); - return this._onmessage(request.params, extra); + return this._onmessage(params, extra); }, ); } @@ -813,11 +842,15 @@ export class AppBridge extends Protocol ) { this.warnIfRequestHandlerReplaced("onopenlink", this._onopenlink, callback); this._onopenlink = callback; - this.replaceRequestHandler( - McpUiOpenLinkRequestSchema, - async (request, extra) => { + this.setRequestHandler( + "ui/open-link", + { + params: McpUiOpenLinkRequestSchema.shape.params, + result: McpUiOpenLinkResultSchema, + }, + async (params, extra) => { if (!this._onopenlink) throw new Error("No onopenlink handler set"); - return this._onopenlink(request.params, extra); + return this._onopenlink(params, extra); }, ); } @@ -885,12 +918,16 @@ export class AppBridge extends Protocol callback, ); this._ondownloadfile = callback; - this.replaceRequestHandler( - McpUiDownloadFileRequestSchema, - async (request, extra) => { + this.setRequestHandler( + "ui/download-file", + { + params: McpUiDownloadFileRequestSchema.shape.params, + result: McpUiDownloadFileResultSchema, + }, + async (params, extra) => { if (!this._ondownloadfile) throw new Error("No ondownloadfile handler set"); - return this._ondownloadfile(request.params, extra); + return this._ondownloadfile(params, extra); }, ); } @@ -985,12 +1022,16 @@ export class AppBridge extends Protocol callback, ); this._onrequestdisplaymode = callback; - this.replaceRequestHandler( - McpUiRequestDisplayModeRequestSchema, - async (request, extra) => { + this.setRequestHandler( + "ui/request-display-mode", + { + params: McpUiRequestDisplayModeRequestSchema.shape.params, + result: McpUiRequestDisplayModeResultSchema, + }, + async (params, extra) => { if (!this._onrequestdisplaymode) throw new Error("No onrequestdisplaymode handler set"); - return this._onrequestdisplaymode(request.params, extra); + return this._onrequestdisplaymode(params, extra); }, ); } @@ -1082,12 +1123,16 @@ export class AppBridge extends Protocol callback, ); this._onupdatemodelcontext = callback; - this.replaceRequestHandler( - McpUiUpdateModelContextRequestSchema, - async (request, extra) => { + this.setRequestHandler( + "ui/update-model-context", + { + params: McpUiUpdateModelContextRequestSchema.shape.params, + result: EmptyResultSchema, + }, + async (params, extra) => { if (!this._onupdatemodelcontext) throw new Error("No onupdatemodelcontext handler set"); - return this._onupdatemodelcontext(request.params, extra); + return this._onupdatemodelcontext(params, extra); }, ); } @@ -1135,13 +1180,11 @@ export class AppBridge extends Protocol ) { this.warnIfRequestHandlerReplaced("oncalltool", this._oncalltool, callback); this._oncalltool = callback; - this.replaceRequestHandler( - CallToolRequestSchema, - async (request, extra) => { - if (!this._oncalltool) throw new Error("No oncalltool handler set"); - return this._oncalltool(request.params, extra); - }, - ); + this.ensureInnerCapabilities({ tools: {} }); + this.setRequestHandler("tools/call", async (request, extra) => { + if (!this._oncalltool) throw new Error("No oncalltool handler set"); + return this._oncalltool(request.params, extra); + }); } /** @@ -1179,12 +1222,9 @@ export class AppBridge extends Protocol extra: RequestHandlerExtra, ) => Promise, ) { - this.setRequestHandler( - CreateMessageRequestSchema, - async (request, extra) => { - return callback(request.params, extra); - }, - ); + this.setRequestHandler("sampling/createMessage", async (request, extra) => { + return callback(request.params, extra); + }); } /** @@ -1260,14 +1300,12 @@ export class AppBridge extends Protocol callback, ); this._onlistresources = callback; - this.replaceRequestHandler( - ListResourcesRequestSchema, - async (request, extra) => { - if (!this._onlistresources) - throw new Error("No onlistresources handler set"); - return this._onlistresources(request.params, extra); - }, - ); + this.ensureInnerCapabilities({ resources: {} }); + this.setRequestHandler("resources/list", async (request, extra) => { + if (!this._onlistresources) + throw new Error("No onlistresources handler set"); + return this._onlistresources(request.params, extra); + }); } /** @@ -1317,8 +1355,9 @@ export class AppBridge extends Protocol callback, ); this._onlistresourcetemplates = callback; - this.replaceRequestHandler( - ListResourceTemplatesRequestSchema, + this.ensureInnerCapabilities({ resources: {} }); + this.setRequestHandler( + "resources/templates/list", async (request, extra) => { if (!this._onlistresourcetemplates) throw new Error("No onlistresourcetemplates handler set"); @@ -1374,14 +1413,12 @@ export class AppBridge extends Protocol callback, ); this._onreadresource = callback; - this.replaceRequestHandler( - ReadResourceRequestSchema, - async (request, extra) => { - if (!this._onreadresource) - throw new Error("No onreadresource handler set"); - return this._onreadresource(request.params, extra); - }, - ); + this.ensureInnerCapabilities({ resources: {} }); + this.setRequestHandler("resources/read", async (request, extra) => { + if (!this._onreadresource) + throw new Error("No onreadresource handler set"); + return this._onreadresource(request.params, extra); + }); } /** @@ -1459,14 +1496,11 @@ export class AppBridge extends Protocol callback, ); this._onlistprompts = callback; - this.replaceRequestHandler( - ListPromptsRequestSchema, - async (request, extra) => { - if (!this._onlistprompts) - throw new Error("No onlistprompts handler set"); - return this._onlistprompts(request.params, extra); - }, - ); + this.ensureInnerCapabilities({ prompts: {} }); + this.setRequestHandler("prompts/list", async (request, extra) => { + if (!this._onlistprompts) throw new Error("No onlistprompts handler set"); + return this._onlistprompts(request.params, extra); + }); } /** @@ -1495,46 +1529,6 @@ export class AppBridge extends Protocol }); } - /** - * Verify that the guest supports the capability required for the given request method. - * @internal - */ - assertCapabilityForMethod(method: AppRequest["method"]): void { - // TODO - } - - /** - * Verify that a request handler is registered and supported for the given method. - * @internal - */ - assertRequestHandlerCapability(method: AppRequest["method"]): void { - // TODO - } - - /** - * Verify that the host supports the capability required for the given notification method. - * @internal - */ - assertNotificationCapability(method: AppNotification["method"]): void { - // TODO - } - - /** - * Verify that task creation is supported for the given request method. - * @internal - */ - protected assertTaskCapability(_method: string): void { - throw new Error("Tasks are not supported in MCP Apps"); - } - - /** - * Verify that task handler is supported for the given method. - * @internal - */ - protected assertTaskHandlerCapability(_method: string): void { - throw new Error("Task handlers are not supported in MCP Apps"); - } - /** * Get the host capabilities passed to the constructor. * @@ -1542,18 +1536,18 @@ export class AppBridge extends Protocol * * @see {@link McpUiHostCapabilities `McpUiHostCapabilities`} for the capabilities structure */ - getCapabilities(): McpUiHostCapabilities { - return this._capabilities; + getHostCapabilities(): McpUiHostCapabilities { + return this._hostCapabilities; } /** * Handle the ui/initialize request from the guest. * @internal */ - private async _oninitialize( - request: McpUiInitializeRequest, + private async _onAppsInitialize( + params: McpUiInitializeRequest["params"], ): Promise { - const requestedVersion = request.params.protocolVersion; + const requestedVersion = params.protocolVersion; if (this._appInfo !== undefined) { console.warn( @@ -1564,8 +1558,8 @@ export class AppBridge extends Protocol ); } - this._appCapabilities = request.params.appCapabilities; - this._appInfo = request.params.appInfo; + this._appCapabilities = params.appCapabilities; + this._appInfo = params.appInfo; const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes( requestedVersion, @@ -1575,7 +1569,7 @@ export class AppBridge extends Protocol return { protocolVersion, - hostCapabilities: this.getCapabilities(), + hostCapabilities: this.getHostCapabilities(), hostInfo: this._hostInfo, hostContext: this._hostContext, }; @@ -1857,11 +1851,7 @@ export class AppBridge extends Protocol * @returns Promise resolving to the tool call result */ callTool(params: CallToolRequest["params"], options?: RequestOptions) { - return this.request( - { method: "tools/call", params }, - CallToolResultSchema, - options, - ); + return this.request({ method: "tools/call", params }, options); } /** @@ -1874,11 +1864,7 @@ export class AppBridge extends Protocol * @returns Promise resolving to the list of tools */ listTools(params: ListToolsRequest["params"], options?: RequestOptions) { - return this.request( - { method: "tools/list", params }, - ListToolsResultSchema, - options, - ); + return this.request({ method: "tools/list", params }, options); } /** @@ -1949,18 +1935,18 @@ export class AppBridge extends Protocol if (!serverCapabilities) { throw new Error("Client server capabilities not available"); } + this.registerCapabilities(proxiedInnerCapabilities(serverCapabilities)); if (serverCapabilities.tools) { this.oncalltool = async (params, extra) => { return this._client!.request( { method: "tools/call", params }, - CallToolResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; if (serverCapabilities.tools.listChanged) { this._client.setNotificationHandler( - ToolListChangedNotificationSchema, + "notifications/tools/list_changed", (n) => this.sendToolListChanged(n.params), ); } @@ -1969,27 +1955,24 @@ export class AppBridge extends Protocol this.onlistresources = async (params, extra) => { return this._client!.request( { method: "resources/list", params }, - ListResourcesResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; this.onlistresourcetemplates = async (params, extra) => { return this._client!.request( { method: "resources/templates/list", params }, - ListResourceTemplatesResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; this.onreadresource = async (params, extra) => { return this._client!.request( { method: "resources/read", params }, - ReadResourceResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; if (serverCapabilities.resources.listChanged) { this._client.setNotificationHandler( - ResourceListChangedNotificationSchema, + "notifications/resources/list_changed", (n) => this.sendResourceListChanged(n.params), ); } @@ -1998,22 +1981,20 @@ export class AppBridge extends Protocol this.onlistprompts = async (params, extra) => { return this._client!.request( { method: "prompts/list", params }, - ListPromptsResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; if (serverCapabilities.prompts.listChanged) { this._client.setNotificationHandler( - PromptListChangedNotificationSchema, + "notifications/prompts/list_changed", (n) => this.sendPromptListChanged(n.params), ); } } } - // MCP-UI specific handlers are registered by the host component - // after the proxy is created. The standard MCP initialization - // (via oninitialized callback set in constructor) handles the ready signal. + // The standard MCP handshake only establishes the temporary inner Server + // role. ui/notifications/initialized remains the public View-ready signal. return super.connect(transport); } From a76e3a76913b9482a5d00342184004ccaab44885 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 15:46:56 -0700 Subject: [PATCH 5/9] refactor: migrate shared SDK surfaces to v2 --- scripts/generate-schemas.ts | 6 +- src/generated/schema.json | 182 +++++++++++++++++++++++------------- src/generated/schema.ts | 2 +- src/message-transport.ts | 8 +- src/react/useApp.tsx | 3 +- src/server/index.ts | 54 +++-------- src/spec.types.ts | 22 ++--- src/types.ts | 130 -------------------------- 8 files changed, 149 insertions(+), 258 deletions(-) diff --git a/scripts/generate-schemas.ts b/scripts/generate-schemas.ts index 109f35247..85225f5c5 100644 --- a/scripts/generate-schemas.ts +++ b/scripts/generate-schemas.ts @@ -12,7 +12,7 @@ * * ts-to-zod generates `import { z } from "zod"`. We rewrite this to * `import { z } from "zod/v4"` because the generated schemas compose with - * schemas imported from `@modelcontextprotocol/sdk/types.js`, which the SDK + * schemas imported from `@modelcontextprotocol/core`, which the SDK * constructs via `zod/v4`. Mixing schemas from the v3 and v4 APIs at runtime * fails with errors like `keyValidator._parse is not a function` (v3 internals * calling into v4 objects, or vice versa). The `zod/v4` subpath is exported by @@ -22,7 +22,7 @@ * * **Problem**: ts-to-zod cannot resolve types imported from external packages. * When it encounters types like `ContentBlock`, `CallToolResult`, `Implementation`, - * `RequestId`, and `Tool` from `@modelcontextprotocol/sdk`, it generates `z.any()` + * `RequestId`, and `Tool` from `@modelcontextprotocol/core`, it generates `z.any()` * as a placeholder. * * **Solution**: Import the schemas from MCP SDK and remove the z.any() placeholders. @@ -193,7 +193,7 @@ function postProcess(content: string): string { `import { z } from "zod/v4"; import { ${mcpImports}, -} from "@modelcontextprotocol/sdk/types.js";`, +} from "@modelcontextprotocol/core";`, ); // 2. Remove z.any() placeholders for external types (now imported from MCP SDK) diff --git a/src/generated/schema.json b/src/generated/schema.json index 80b4ac60d..5f2124a95 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -596,7 +596,9 @@ "propertyNames": { "type": "string" }, - "additionalProperties": {} + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } }, "required": { "type": "array", @@ -611,25 +613,10 @@ "outputSchema": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "$schema": { + "type": "string" } }, - "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -1286,7 +1273,40 @@ } }, "required": ["method", "params"], - "additionalProperties": false + "additionalProperties": false, + "$defs": { + "__schema0": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + } + ] + } + } }, "McpUiHostContext": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -1359,7 +1379,9 @@ "propertyNames": { "type": "string" }, - "additionalProperties": {} + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } }, "required": { "type": "array", @@ -1374,25 +1396,10 @@ "outputSchema": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "$schema": { + "type": "string" } }, - "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -2044,7 +2051,40 @@ "additionalProperties": false } }, - "additionalProperties": {} + "additionalProperties": {}, + "$defs": { + "__schema0": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + } + ] + } + } }, "McpUiHostCss": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -2942,7 +2982,9 @@ "propertyNames": { "type": "string" }, - "additionalProperties": {} + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } }, "required": { "type": "array", @@ -2957,25 +2999,10 @@ "outputSchema": { "type": "object", "properties": { - "type": { - "type": "string", - "const": "object" - }, - "properties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "$schema": { + "type": "string" } }, - "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -3637,7 +3664,40 @@ "hostCapabilities", "hostContext" ], - "additionalProperties": {} + "additionalProperties": {}, + "$defs": { + "__schema0": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/$defs/__schema0" + } + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/__schema0" + } + } + ] + } + } }, "McpUiInitializedNotification": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -5669,13 +5729,7 @@ ] } }, - "structuredContent": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, + "structuredContent": {}, "isError": { "type": "boolean" } diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 43687374e..341c7b488 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -10,7 +10,7 @@ import { RequestIdSchema, ResourceLinkSchema, ToolSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/core"; /** * @description Color theme preference for the host environment. diff --git a/src/message-transport.ts b/src/message-transport.ts index 9d195435a..94835401f 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -1,12 +1,10 @@ -import { +import { JSONRPCMessageSchema } from "@modelcontextprotocol/core"; +import type { JSONRPCMessage, - JSONRPCMessageSchema, MessageExtraInfo, -} from "@modelcontextprotocol/sdk/types.js"; -import { Transport, TransportSendOptions, -} from "@modelcontextprotocol/sdk/shared/transport.js"; +} from "@modelcontextprotocol/client"; import { TOOL_INPUT_PARTIAL_METHOD } from "./spec.types"; /** diff --git a/src/react/useApp.tsx b/src/react/useApp.tsx index 61892464a..0e64a74d6 100644 --- a/src/react/useApp.tsx +++ b/src/react/useApp.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; -import { Implementation } from "@modelcontextprotocol/sdk/types.js"; -import { Client } from "@modelcontextprotocol/sdk/client"; +import type { Implementation } from "@modelcontextprotocol/client"; import { App, AppOptions, diff --git a/src/server/index.ts b/src/server/index.ts index c90514acd..b8b3ad026 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -40,24 +40,17 @@ import { McpUiClientCapabilities, } from "../app.js"; import type { - BaseToolCallback, + ClientCapabilities, McpServer, + ReadResourceResult, RegisteredTool, ResourceMetadata, + StandardSchemaWithJSON, + ToolAnnotations, ToolCallback, ReadResourceCallback as _ReadResourceCallback, RegisteredResource, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - AnySchema, - ZodRawShapeCompat, -} from "@modelcontextprotocol/sdk/server/zod-compat.js"; -import type { StandardSchemaWithJSON } from "../standard-schema"; -import type { - ClientCapabilities, - ReadResourceResult, - ToolAnnotations, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; // Re-exports for convenience export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE }; @@ -70,8 +63,8 @@ export type { ResourceMetadata, ToolCallback }; export interface ToolConfig { title?: string; description?: string; - inputSchema?: ZodRawShapeCompat | StandardSchemaWithJSON; - outputSchema?: ZodRawShapeCompat | StandardSchemaWithJSON; + inputSchema?: StandardSchemaWithJSON; + outputSchema?: StandardSchemaWithJSON; annotations?: ToolAnnotations; _meta?: Record; } @@ -215,9 +208,8 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * @see {@link registerAppResource `registerAppResource`} to register the HTML resource referenced by the tool */ export function registerAppTool< - OutputArgs extends ZodRawShapeCompat | StandardSchemaWithJSON, - InputArgs extends undefined | ZodRawShapeCompat | StandardSchemaWithJSON = - undefined, + OutputArgs extends StandardSchemaWithJSON, + InputArgs extends StandardSchemaWithJSON | undefined = undefined, >( server: Pick, name: string, @@ -225,15 +217,7 @@ export function registerAppTool< inputSchema?: InputArgs; outputSchema?: OutputArgs; }, - // The widened constraint signals the v2 API shape, but NOTE: McpServer in - // sdk@1.x still calls zod internals at runtime, so non-zod schemas will fail - // here until we depend on sdk v2. Zod (which all current callers use) is - // unaffected. The cast below bridges the 1.x type signature. - cb: ToolCallback< - InputArgs extends undefined | ZodRawShapeCompat | AnySchema - ? InputArgs - : AnySchema - >, + cb: ToolCallback, ): RegisteredTool { // Normalize metadata for backward compatibility: // - If _meta.ui.resourceUri is set, also set the legacy flat key @@ -251,14 +235,7 @@ export function registerAppTool< normalizedMeta = { ...meta, ui: { ...uiMeta, resourceUri: legacyUri } }; } - // Cast bridges the widened StandardSchemaWithJSON constraint to the - // sdk@1.x zod-typed signature. Drops once we depend on sdk v2. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return server.registerTool( - name, - { ...config, _meta: normalizedMeta } as any, - cb as any, - ); + return server.registerTool(name, { ...config, _meta: normalizedMeta }, cb); } export type McpUiReadResourceResult = ReadResourceResult & { @@ -418,10 +395,6 @@ export const EXTENSION_ID = "io.modelcontextprotocol/ui"; * This helper retrieves the capability object from the `extensions` field * where MCP Apps advertises its support. * - * Note: The `clientCapabilities` parameter extends the SDK's `ClientCapabilities` - * type with an `extensions` field (pending SEP-1724). Once `extensions` is added - * to the SDK, this can use `ClientCapabilities` directly. - * * @param clientCapabilities - The client capabilities from the initialize response * @returns The MCP Apps capability settings, or `undefined` if not supported * @@ -456,10 +429,7 @@ export const EXTENSION_ID = "io.modelcontextprotocol/ui"; * ``` */ export function getUiCapability( - clientCapabilities: - | (ClientCapabilities & { extensions?: Record }) - | null - | undefined, + clientCapabilities: ClientCapabilities | null | undefined, ): McpUiClientCapabilities | undefined { if (!clientCapabilities) { return undefined; diff --git a/src/spec.types.ts b/src/spec.types.ts index 7a8b33761..8b03de94f 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -18,7 +18,7 @@ import type { RequestId, ResourceLink, Tool, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/client"; /** * Current protocol version supported by this SDK. @@ -163,8 +163,8 @@ export interface McpUiOpenLinkResult { /** @description True if the host failed to open the URL (e.g., due to security policy). */ isError?: boolean; /** - * Index signature required for MCP SDK `Protocol` class compatibility. - * Note: The generated schema uses passthrough() to allow additional properties. + * Additional result properties are preserved for protocol extensibility. + * The generated schema uses passthrough() to allow them. */ [key: string]: unknown; } @@ -195,8 +195,8 @@ export interface McpUiDownloadFileResult { /** @description True if the download failed (e.g., user cancelled or host denied). */ isError?: boolean; /** - * Index signature required for MCP SDK `Protocol` class compatibility. - * Note: The generated schema uses passthrough() to allow additional properties. + * Additional result properties are preserved for protocol extensibility. + * The generated schema uses passthrough() to allow them. */ [key: string]: unknown; } @@ -223,8 +223,8 @@ export interface McpUiMessageResult { /** @description True if the host rejected or failed to deliver the message. */ isError?: boolean; /** - * Index signature required for MCP SDK `Protocol` class compatibility. - * Note: The generated schema uses passthrough() to allow additional properties. + * Additional result properties are preserved for protocol extensibility. + * The generated schema uses passthrough() to allow them. */ [key: string]: unknown; } @@ -454,7 +454,7 @@ export interface McpUiResourceTeardownRequest { */ export interface McpUiResourceTeardownResult { /** - * Index signature required for MCP SDK `Protocol` class compatibility. + * Additional result properties are preserved for protocol extensibility. */ [key: string]: unknown; } @@ -577,8 +577,8 @@ export interface McpUiInitializeResult { /** @description Rich context about the host environment. */ hostContext: McpUiHostContext; /** - * Index signature required for MCP SDK `Protocol` class compatibility. - * Note: The generated schema uses passthrough() to allow additional properties. + * Additional result properties are preserved for protocol extensibility. + * The generated schema uses passthrough() to allow them. */ [key: string]: unknown; } @@ -752,7 +752,7 @@ export interface McpUiRequestDisplayModeResult { /** @description The display mode that was actually set. May differ from requested if not supported. */ mode: McpUiDisplayMode; /** - * Index signature required for MCP SDK `Protocol` class compatibility. + * Additional result properties are preserved for protocol extensibility. * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; diff --git a/src/types.ts b/src/types.ts index 7fc6b7188..655812bd3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -69,33 +69,6 @@ export { type McpUiClientCapabilities, } from "./spec.types.js"; -// Import types needed for protocol type unions (not re-exported, just used internally) -import type { - McpUiInitializeRequest, - McpUiOpenLinkRequest, - McpUiDownloadFileRequest, - McpUiMessageRequest, - McpUiUpdateModelContextRequest, - McpUiResourceTeardownRequest, - McpUiRequestDisplayModeRequest, - McpUiHostContextChangedNotification, - McpUiToolInputNotification, - McpUiToolInputPartialNotification, - McpUiToolResultNotification, - McpUiToolCancelledNotification, - McpUiSandboxResourceReadyNotification, - McpUiInitializedNotification, - McpUiSizeChangedNotification, - McpUiSandboxProxyReadyNotification, - McpUiRequestTeardownNotification, - McpUiInitializeResult, - McpUiOpenLinkResult, - McpUiDownloadFileResult, - McpUiMessageResult, - McpUiResourceTeardownResult, - McpUiRequestDisplayModeResult, -} from "./spec.types.js"; - // Re-export all schemas from generated/schema.ts (already PascalCase) export { McpUiThemeSchema, @@ -135,106 +108,3 @@ export { McpUiToolVisibilitySchema, McpUiToolMetaSchema, } from "./generated/schema.js"; - -// Re-export SDK types used in protocol type unions -import { - CallToolRequest, - CallToolResult, - CreateMessageRequest, - CreateMessageResult, - CreateMessageResultWithTools, - EmptyResult, - ListPromptsRequest, - ListPromptsResult, - ListResourcesRequest, - ListResourcesResult, - ListResourceTemplatesRequest, - ListResourceTemplatesResult, - ListToolsRequest, - ListToolsResult, - LoggingMessageNotification, - PingRequest, - PromptListChangedNotification, - ReadResourceRequest, - ReadResourceResult, - ResourceListChangedNotification, - ToolListChangedNotification, -} from "@modelcontextprotocol/sdk/types.js"; - -/** - * All request types in the MCP Apps protocol. - * - * Includes: - * - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode) - * - MCP server requests forwarded from the app (tools/call, tools/list, resources/list, - * resources/templates/list, resources/read, prompts/list) - * - MCP client requests forwarded to the host (sampling/createMessage) - * - Protocol requests (ping) - */ -export type AppRequest = - | McpUiInitializeRequest - | McpUiOpenLinkRequest - | McpUiDownloadFileRequest - | McpUiMessageRequest - | McpUiUpdateModelContextRequest - | McpUiResourceTeardownRequest - | McpUiRequestDisplayModeRequest - | CallToolRequest - | ListToolsRequest - | ListResourcesRequest - | ListResourceTemplatesRequest - | ReadResourceRequest - | ListPromptsRequest - | CreateMessageRequest - | PingRequest; - -/** - * All notification types in the MCP Apps protocol. - * - * Host to app: - * - Tool lifecycle (input, input-partial, result, cancelled) - * - Host context changes - * - MCP list changes (tools, resources, prompts) - * - Sandbox resource ready - * - * App to host: - * - Initialized, size-changed, sandbox-proxy-ready, request-teardown - * - Logging messages - */ -export type AppNotification = - // Sent to app - | McpUiHostContextChangedNotification - | McpUiToolInputNotification - | McpUiToolInputPartialNotification - | McpUiToolResultNotification - | McpUiToolCancelledNotification - | McpUiSandboxResourceReadyNotification - | ToolListChangedNotification - | ResourceListChangedNotification - | PromptListChangedNotification - // Received from app - | McpUiInitializedNotification - | McpUiSizeChangedNotification - | McpUiSandboxProxyReadyNotification - | McpUiRequestTeardownNotification - | LoggingMessageNotification; - -/** - * All result types in the MCP Apps protocol. - */ -export type AppResult = - | McpUiInitializeResult - | McpUiOpenLinkResult - | McpUiDownloadFileResult - | McpUiMessageResult - | McpUiResourceTeardownResult - | McpUiRequestDisplayModeResult - | CallToolResult - | ListToolsResult - | ListResourcesResult - | ListResourceTemplatesResult - | ReadResourceResult - | ListPromptsResult - | CreateMessageResult - | CreateMessageResultWithTools - | EmptyResult; From f94cb38b0b6453476cb45e3edccf606505be7708 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 17:07:30 -0700 Subject: [PATCH 6/9] refactor: finish MCP SDK v2 migration --- AGENTS.md | 8 +- README.md | 10 +- build.bun.ts | 12 +- docs/authorization.md | 2 +- docs/migrate_from_openai_apps.md | 8 +- docs/overview.md | 8 +- docs/patterns.md | 11 +- docs/patterns.tsx | 17 +-- docs/quickstart.md | 19 +-- docs/testing-mcp-apps.md | 4 + examples/basic-host/package.json | 2 +- examples/basic-host/src/implementation.ts | 12 +- examples/basic-host/src/index.tsx | 2 +- examples/basic-server-preact/main.ts | 10 +- examples/basic-server-preact/package.json | 3 +- examples/basic-server-preact/server.ts | 10 +- examples/basic-server-preact/src/mcp-app.tsx | 2 +- examples/basic-server-react/main.ts | 10 +- examples/basic-server-react/package.json | 3 +- examples/basic-server-react/server.ts | 10 +- examples/basic-server-react/src/mcp-app.tsx | 2 +- examples/basic-server-solid/main.ts | 10 +- examples/basic-server-solid/package.json | 3 +- examples/basic-server-solid/server.ts | 10 +- examples/basic-server-solid/src/mcp-app.tsx | 2 +- examples/basic-server-svelte/main.ts | 10 +- examples/basic-server-svelte/package.json | 3 +- examples/basic-server-svelte/server.ts | 10 +- examples/basic-server-svelte/src/App.svelte | 2 +- examples/basic-server-vanillajs/main.ts | 10 +- examples/basic-server-vanillajs/package.json | 3 +- examples/basic-server-vanillajs/server.ts | 9 +- .../basic-server-vanillajs/src/mcp-app.ts | 2 +- examples/basic-server-vue/main.ts | 10 +- examples/basic-server-vue/package.json | 3 +- examples/basic-server-vue/server.ts | 10 +- examples/basic-server-vue/src/App.vue | 2 +- examples/budget-allocator-server/main.ts | 10 +- examples/budget-allocator-server/package.json | 2 +- examples/budget-allocator-server/server.ts | 12 +- examples/cohort-heatmap-server/main.ts | 10 +- examples/cohort-heatmap-server/package.json | 2 +- examples/cohort-heatmap-server/server.ts | 10 +- examples/customer-segmentation-server/main.ts | 10 +- .../customer-segmentation-server/package.json | 2 +- .../customer-segmentation-server/server.ts | 14 +-- examples/debug-server/main.ts | 10 +- examples/debug-server/package.json | 2 +- examples/debug-server/server.ts | 10 +- examples/integration-server/main.ts | 10 +- examples/integration-server/package.json | 3 +- examples/integration-server/server.ts | 14 +-- examples/integration-server/src/mcp-app.tsx | 2 +- examples/lazy-auth-server/package.json | 5 +- examples/lazy-auth-server/server.ts | 29 ++--- examples/map-server/main.ts | 10 +- examples/map-server/package.json | 3 +- examples/map-server/server.ts | 18 +-- examples/map-server/src/mcp-app.ts | 2 +- examples/pdf-server/main.ts | 10 +- examples/pdf-server/package.json | 3 +- examples/pdf-server/server.test.ts | 3 +- examples/pdf-server/server.ts | 57 +++++---- examples/pdf-server/src/mcp-app.ts | 6 +- examples/qr-server/package.json | 3 +- examples/quickstart/main.ts | 10 +- examples/quickstart/package.json | 5 +- examples/quickstart/server.ts | 5 +- examples/say-server/package.json | 3 +- examples/scenario-modeler-server/main.ts | 10 +- examples/scenario-modeler-server/package.json | 2 +- examples/scenario-modeler-server/server.ts | 14 +-- examples/shadertoy-server/main.ts | 10 +- examples/shadertoy-server/package.json | 2 +- examples/shadertoy-server/server.ts | 10 +- examples/sheet-music-server/main.ts | 10 +- examples/sheet-music-server/package.json | 2 +- examples/sheet-music-server/server.ts | 10 +- examples/system-monitor-server/main.ts | 10 +- examples/system-monitor-server/package.json | 2 +- examples/system-monitor-server/server.ts | 18 +-- examples/threejs-server/main.ts | 10 +- examples/threejs-server/package.json | 3 +- examples/threejs-server/server.ts | 12 +- .../threejs-server/src/mcp-app-wrapper.tsx | 2 +- examples/transcript-server/main.ts | 10 +- examples/transcript-server/package.json | 2 +- examples/transcript-server/server.ts | 13 +- examples/video-resource-server/main.ts | 10 +- examples/video-resource-server/package.json | 3 +- examples/video-resource-server/server.ts | 12 +- examples/video-resource-server/src/mcp-app.ts | 2 +- examples/wiki-explorer-server/main.ts | 10 +- examples/wiki-explorer-server/package.json | 3 +- examples/wiki-explorer-server/server.ts | 10 +- examples/wiki-explorer-server/src/mcp-app.ts | 2 +- package-lock.json | 95 +++++++++------ package.json | 2 - .../skills/add-app-to-server/SKILL.md | 111 +++++++++++------- .../mcp-apps/skills/convert-web-app/SKILL.md | 57 +++++---- .../mcp-apps/skills/create-mcp-app/SKILL.md | 5 +- scripts/typedoc-plugin-base-sdk-links.mjs | 16 +++ src/app-bridge.examples.ts | 36 +++--- src/app-bridge.ts | 62 +++++----- src/app.examples.ts | 9 +- src/app.ts | 19 ++- src/events.ts | 5 +- src/message-transport.test.ts | 2 +- src/server/index.examples.ts | 11 +- src/server/index.test.ts | 2 +- src/server/index.ts | 4 +- typedoc.config.mjs | 25 ++++ 112 files changed, 684 insertions(+), 540 deletions(-) create mode 100644 scripts/typedoc-plugin-base-sdk-links.mjs diff --git a/AGENTS.md b/AGENTS.md index 6bfc8cdd6..f850aa3e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \ ### Key Source Files -- `src/app.ts` - `App` class extends MCP Protocol, handles guest initialization, tool calls, messaging -- `src/app-bridge.ts` - `AppBridge` class for hosts, proxies MCP requests, sends tool input/results to guests +- `src/app.ts` - `App` subclasses the base MCP SDK `Client`, handles View initialization, tool calls, and messaging +- `src/app-bridge.ts` - `AppBridge` subclasses the base MCP SDK `Server` for the inner iframe channel and proxies through a separate outer `Client` - `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata - `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build) - `src/message-transport.ts` - `PostMessageTransport` for iframe communication @@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \ ### Protocol Flow ``` -View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server +View (App/Client) <--PostMessageTransport--> Host (AppBridge/Server) <--separate MCP Client--> MCP Server ``` 1. Host creates iframe with view HTML 2. View creates `App` instance and calls `connect()` with `PostMessageTransport` -3. View sends `ui/initialize` request, receives host capabilities and context +3. View and Host complete the temporary inner base MCP handshake, then the View sends `ui/initialize` and receives authoritative Apps capabilities and context 4. Host sends `sendToolInput()` with tool arguments after initialization 5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()` 6. Host sends `sendToolResult()` when tool execution completes diff --git a/README.md b/README.md index 2fd89ffee..b3f68e77c 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,17 @@ resources: ## Getting Started ```bash -npm install -S @modelcontextprotocol/ext-apps +npm install -S @modelcontextprotocol/ext-apps \ + @modelcontextprotocol/client@2.0.0-beta.4 \ + @modelcontextprotocol/server@2.0.0-beta.4 \ + @modelcontextprotocol/core@2.0.0-beta.4 \ + zod@^4.2.0 ``` +This release uses the split base MCP SDK v2 packages. Install all three at the +exact published beta.4 version so the Apps SDK and your MCP client/server share +one compatible protocol implementation. + **New here?** Start with the [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html) to build your first MCP App. diff --git a/build.bun.ts b/build.bun.ts index a129d5ddd..837279ff1 100644 --- a/build.bun.ts +++ b/build.bun.ts @@ -31,9 +31,15 @@ function buildJs( }); } -// zod is a peerDependency — keep it external so consumers share a single -// zod instance (instanceof ZodError / schema.extend() break with duplicate copies). -const PEER_EXTERNALS = ["@modelcontextprotocol/sdk", "zod"]; +// Peer dependencies stay external in the standard entry points so consumers +// share one base MCP SDK and Zod instance. The *-with-deps entry points keep +// bundling these dependencies for standalone browser use. +const PEER_EXTERNALS = [ + "@modelcontextprotocol/client", + "@modelcontextprotocol/core", + "@modelcontextprotocol/server", + "zod", +]; await Promise.all([ buildJs("src/app.ts", { diff --git a/docs/authorization.md b/docs/authorization.md index 59fc26cde..df7adc6af 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -150,7 +150,7 @@ registerAppTool( "get_account_balance", { description: "Get account balance", - inputSchema: { accountId: z.string() }, + inputSchema: z.object({ accountId: z.string() }), }, async ({ accountId }) => { if (!authInfo) { diff --git a/docs/migrate_from_openai_apps.md b/docs/migrate_from_openai_apps.md index d91281b41..22d5d9f0c 100644 --- a/docs/migrate_from_openai_apps.md +++ b/docs/migrate_from_openai_apps.md @@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun #### Before (OpenAI) ```typescript -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { z } from "zod"; function createServer() { @@ -78,7 +78,7 @@ function createServer() { { title: "Shopping Cart", description: "Display the user's shopping cart", - inputSchema: { userId: z.string() }, + inputSchema: z.object({ userId: z.string() }), annotations: { readOnlyHint: true }, _meta: { "openai/outputTemplate": "ui://view/cart.html", @@ -126,7 +126,7 @@ function createServer() { #### After (MCP Apps) ```typescript -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import { registerAppTool, registerAppResource, @@ -144,7 +144,7 @@ function createServer() { { title: "Shopping Cart", description: "Display the user's shopping cart", - inputSchema: { userId: z.string() }, + inputSchema: z.object({ userId: z.string() }), annotations: { readOnlyHint: true }, _meta: { ui: { resourceUri: "ui://view/cart.html" } }, }, diff --git a/docs/overview.md b/docs/overview.md index 28f5b2379..317ab4300 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -56,7 +56,11 @@ flowchart LR - **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them. - **View** — The UI running inside a sandboxed iframe. It receives tool data from the Host and can call server tools or send messages back to the chat. -The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server. +The View's `App` subclasses the base MCP SDK `Client`. The Host's `AppBridge` +subclasses the base MCP SDK `Server` for the inner iframe channel, while a +separate outer `Client` connects the Host to the actual MCP Server. Keeping +those two connections separate prevents iframe negotiation and capabilities +from leaking into the server connection. ## Lifecycle @@ -93,7 +97,7 @@ sequenceDiagram ``` 1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server. -2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data. +2. **Initialization** — When a UI tool is called, the Host renders the iframe. The SDK first completes its temporary inner base MCP handshake, then the View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). `ui/initialize` remains authoritative for Apps capabilities and identity, and `ui/notifications/initialized` remains the public View-ready signal. 3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context. 4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context. 5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources. diff --git a/docs/patterns.md b/docs/patterns.md index 87a6d8232..884de2651 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -19,7 +19,7 @@ registerAppTool( "update-quantity", { description: "Update item quantity in cart", - inputSchema: { itemId: z.string(), quantity: z.number() }, + inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), _meta: { ui: { resourceUri: "ui://shop/cart.html", @@ -127,14 +127,14 @@ registerAppTool( { title: "Read Data Bytes", description: "Load binary data in chunks", - inputSchema: { + inputSchema: z.object({ id: z.string().describe("Resource identifier"), offset: z.number().min(0).default(0).describe("Byte offset"), byteCount: z .number() .default(MAX_CHUNK_BYTES) .describe("Bytes to read"), - }, + }), outputSchema: DataChunkSchema, // Hidden from model - only callable by the App _meta: { ui: { visibility: ["app"] } }, @@ -281,10 +281,7 @@ server.registerResource( ```ts source="./patterns.tsx#binaryBlobResourceClient" -const result = await app.request( - { method: "resources/read", params: { uri: `video://${videoId}` } }, - ReadResourceResultSchema, -); +const result = await app.readServerResource({ uri: `video://${videoId}` }); const content = result.contents[0]; if (!content || !("blob" in content)) { diff --git a/docs/patterns.tsx b/docs/patterns.tsx index 6b876926b..bb9eaa492 100644 --- a/docs/patterns.tsx +++ b/docs/patterns.tsx @@ -17,16 +17,12 @@ import { randomUUID } from "node:crypto"; import type { CallToolResult, ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; -import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; import type { McpUiHostContext } from "../src/types.js"; import { useEffect, useState } from "react"; import { useApp } from "../src/react/index.js"; import { registerAppTool } from "../src/server/index.js"; -import { - McpServer, - ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server"; import { z } from "zod"; /** @@ -117,14 +113,14 @@ function chunkedDataServer(server: McpServer) { { title: "Read Data Bytes", description: "Load binary data in chunks", - inputSchema: { + inputSchema: z.object({ id: z.string().describe("Resource identifier"), offset: z.number().min(0).default(0).describe("Byte offset"), byteCount: z .number() .default(MAX_CHUNK_BYTES) .describe("Bytes to read"), - }, + }), outputSchema: DataChunkSchema, // Hidden from model - only callable by the App _meta: { ui: { visibility: ["app"] } }, @@ -252,10 +248,7 @@ function binaryBlobResourceServer( */ async function binaryBlobResourceClient(app: App, videoId: string) { //#region binaryBlobResourceClient - const result = await app.request( - { method: "resources/read", params: { uri: `video://${videoId}` } }, - ReadResourceResultSchema, - ); + const result = await app.readServerResource({ uri: `video://${videoId}` }); const content = result.contents[0]; if (!content || !("blob" in content)) { diff --git a/docs/quickstart.md b/docs/quickstart.md index 3e00796c5..eb6b096cb 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -21,7 +21,7 @@ This tutorial assumes you've built an MCP server before and are comfortable with We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server. -You'll also need Node.js 18+. +You'll also need Node.js 20+. ## 1. Set up the project @@ -37,7 +37,7 @@ Install the dependencies you'll need: ```bash npm init -y -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.4 @modelcontextprotocol/core@2.0.0-beta.4 @modelcontextprotocol/server@2.0.0-beta.4 @modelcontextprotocol/node@2.0.0-beta.4 @modelcontextprotocol/express@2.0.0-beta.4 zod@^4.2.0 express cors npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env ``` @@ -178,9 +178,10 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; +import { z } from "zod"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -205,7 +206,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async () => { @@ -240,10 +241,10 @@ export function createServer(): McpServer { ```ts source="../examples/quickstart/main.ts" -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -263,7 +264,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/docs/testing-mcp-apps.md b/docs/testing-mcp-apps.md index df1396c5f..e5d82b53e 100644 --- a/docs/testing-mcp-apps.md +++ b/docs/testing-mcp-apps.md @@ -28,6 +28,10 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex cd examples/basic-host ``` + The root install runs the package build, including type-checking the + documentation snippets against the exact base MCP SDK beta.4 packages. Do + not bypass a failed build before testing the host. + 2. Start basic-host, pointing it to your MCP server: ```bash diff --git a/examples/basic-host/package.json b/examples/basic-host/package.json index 1b45dcf18..583d2ffa9 100644 --- a/examples/basic-host/package.json +++ b/examples/basic-host/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/client": "2.0.0-beta.4", "react": "^19.2.0", "react-dom": "^19.2.0", "zod": "^4.1.13" diff --git a/examples/basic-host/src/implementation.ts b/examples/basic-host/src/implementation.ts index 31e36983e..5f680ffdc 100644 --- a/examples/basic-host/src/implementation.ts +++ b/examples/basic-host/src/implementation.ts @@ -1,8 +1,12 @@ import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute, type McpUiUpdateModelContextRequest, type McpUiMessageRequest } from "@modelcontextprotocol/ext-apps/app-bridge"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import type { CallToolResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js"; +import { + Client, + SSEClientTransport, + StreamableHTTPClientTransport, + type CallToolResult, + type Resource, + type Tool, +} from "@modelcontextprotocol/client"; import { getTheme, onThemeChange } from "./theme"; import { HOST_STYLE_VARIABLES } from "./host-styles"; diff --git a/examples/basic-host/src/index.tsx b/examples/basic-host/src/index.tsx index 3d488a792..00c0d52b2 100644 --- a/examples/basic-host/src/index.tsx +++ b/examples/basic-host/src/index.tsx @@ -1,5 +1,5 @@ import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge"; -import type { Tool } from "@modelcontextprotocol/sdk/types.js"; +import type { Tool } from "@modelcontextprotocol/client"; import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation"; diff --git a/examples/basic-server-preact/main.ts b/examples/basic-server-preact/main.ts index 76426326d..ffdff5266 100644 --- a/examples/basic-server-preact/main.ts +++ b/examples/basic-server-preact/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-preact/package.json b/examples/basic-server-preact/package.json index 1a6323233..0187827f8 100644 --- a/examples/basic-server-preact/package.json +++ b/examples/basic-server-preact/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "preact": "^10.0.0", diff --git a/examples/basic-server-preact/server.ts b/examples/basic-server-preact/server.ts index bf14ea599..dfec94aa1 100644 --- a/examples/basic-server-preact/server.ts +++ b/examples/basic-server-preact/server.ts @@ -1,8 +1,12 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -28,7 +32,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-preact/src/mcp-app.tsx b/examples/basic-server-preact/src/mcp-app.tsx index a8bdc68b8..351a8ec21 100644 --- a/examples/basic-server-preact/src/mcp-app.tsx +++ b/examples/basic-server-preact/src/mcp-app.tsx @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { useCallback, useEffect, useState } from "preact/hooks"; import { render } from "preact"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-react/main.ts b/examples/basic-server-react/main.ts index ec187b68a..21c5e18af 100644 --- a/examples/basic-server-react/main.ts +++ b/examples/basic-server-react/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-react/package.json b/examples/basic-server-react/package.json index 821fc9603..dae7665e9 100644 --- a/examples/basic-server-react/package.json +++ b/examples/basic-server-react/package.json @@ -34,10 +34,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index 23f6dda5a..d77c908c4 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,8 +1,12 @@ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") @@ -29,7 +33,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-react/src/mcp-app.tsx b/examples/basic-server-react/src/mcp-app.tsx index 6bb554025..2b5fbef9a 100644 --- a/examples/basic-server-react/src/mcp-app.tsx +++ b/examples/basic-server-react/src/mcp-app.tsx @@ -3,7 +3,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { StrictMode, useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-solid/main.ts b/examples/basic-server-solid/main.ts index c8d9de225..095ef8dfd 100644 --- a/examples/basic-server-solid/main.ts +++ b/examples/basic-server-solid/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-solid/package.json b/examples/basic-server-solid/package.json index 997bc60f6..5d9140761 100644 --- a/examples/basic-server-solid/package.json +++ b/examples/basic-server-solid/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "solid-js": "1.9.10", diff --git a/examples/basic-server-solid/server.ts b/examples/basic-server-solid/server.ts index 2ed2d9356..addb52260 100644 --- a/examples/basic-server-solid/server.ts +++ b/examples/basic-server-solid/server.ts @@ -1,8 +1,12 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -28,7 +32,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-solid/src/mcp-app.tsx b/examples/basic-server-solid/src/mcp-app.tsx index 66d47f3f6..2d78b7b33 100644 --- a/examples/basic-server-solid/src/mcp-app.tsx +++ b/examples/basic-server-solid/src/mcp-app.tsx @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { createEffect, createSignal, onMount, Show } from "solid-js"; import { render } from "solid-js/web"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-svelte/main.ts b/examples/basic-server-svelte/main.ts index 6c50a254f..734ec0cdb 100644 --- a/examples/basic-server-svelte/main.ts +++ b/examples/basic-server-svelte/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-svelte/package.json b/examples/basic-server-svelte/package.json index e83772f20..18bf7abed 100644 --- a/examples/basic-server-svelte/package.json +++ b/examples/basic-server-svelte/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "svelte": "^5.0.0", diff --git a/examples/basic-server-svelte/server.ts b/examples/basic-server-svelte/server.ts index da603ca8d..941428514 100644 --- a/examples/basic-server-svelte/server.ts +++ b/examples/basic-server-svelte/server.ts @@ -1,8 +1,12 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -28,7 +32,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-svelte/src/App.svelte b/examples/basic-server-svelte/src/App.svelte index fde128dde..cabcc317d 100644 --- a/examples/basic-server-svelte/src/App.svelte +++ b/examples/basic-server-svelte/src/App.svelte @@ -7,7 +7,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; function extractTime(result: CallToolResult): string { const { text } = result.content?.find((c) => c.type === "text")!; diff --git a/examples/basic-server-vanillajs/main.ts b/examples/basic-server-vanillajs/main.ts index 286fa34f0..4de733187 100644 --- a/examples/basic-server-vanillajs/main.ts +++ b/examples/basic-server-vanillajs/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-vanillajs/package.json b/examples/basic-server-vanillajs/package.json index 64e1758a5..6bb75af46 100644 --- a/examples/basic-server-vanillajs/package.json +++ b/examples/basic-server-vanillajs/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 5b2daf704..693d86148 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,6 +1,9 @@ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -29,7 +32,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), outputSchema: z.object({ time: z.string(), }), diff --git a/examples/basic-server-vanillajs/src/mcp-app.ts b/examples/basic-server-vanillajs/src/mcp-app.ts index c3168c214..6b5084a3a 100644 --- a/examples/basic-server-vanillajs/src/mcp-app.ts +++ b/examples/basic-server-vanillajs/src/mcp-app.ts @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import "./global.css"; import "./mcp-app.css"; diff --git a/examples/basic-server-vue/main.ts b/examples/basic-server-vue/main.ts index 669a07189..d77426919 100644 --- a/examples/basic-server-vue/main.ts +++ b/examples/basic-server-vue/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-vue/package.json b/examples/basic-server-vue/package.json index e4cd0b431..3b34aa686 100644 --- a/examples/basic-server-vue/package.json +++ b/examples/basic-server-vue/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "vue": "^3.5.0", diff --git a/examples/basic-server-vue/server.ts b/examples/basic-server-vue/server.ts index 5aa72de03..9adb9111e 100644 --- a/examples/basic-server-vue/server.ts +++ b/examples/basic-server-vue/server.ts @@ -1,8 +1,12 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -28,7 +32,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-vue/src/App.vue b/examples/basic-server-vue/src/App.vue index 196ad64da..a45c26208 100644 --- a/examples/basic-server-vue/src/App.vue +++ b/examples/basic-server-vue/src/App.vue @@ -7,7 +7,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; function extractTime(result: CallToolResult): string { const { text } = result.content?.find((c) => c.type === "text")!; diff --git a/examples/budget-allocator-server/main.ts b/examples/budget-allocator-server/main.ts index cc488f0b1..02c03cb59 100644 --- a/examples/budget-allocator-server/main.ts +++ b/examples/budget-allocator-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/budget-allocator-server/package.json b/examples/budget-allocator-server/package.json index 8af1a6c6e..5d4fe8ebb 100644 --- a/examples/budget-allocator-server/package.json +++ b/examples/budget-allocator-server/package.json @@ -29,7 +29,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 04fba11fe..01f5dbfb6 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -4,11 +4,11 @@ * Provides budget configuration, 24 months of historical allocation data, * and industry benchmarks by company stage. */ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -270,7 +270,7 @@ export function createServer(): McpServer { title: "Get Budget Data", description: "Returns budget configuration with 24 months of historical allocations and industry benchmarks by company stage. The widget is interactive and exposes tools for reading/modifying allocations, adjusting budgets, and comparing against industry benchmarks.", - inputSchema: {}, + inputSchema: z.object({}), outputSchema: BudgetDataResponseSchema, _meta: { ui: { resourceUri } }, }, diff --git a/examples/cohort-heatmap-server/main.ts b/examples/cohort-heatmap-server/main.ts index d59903b34..8b33603ba 100644 --- a/examples/cohort-heatmap-server/main.ts +++ b/examples/cohort-heatmap-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/cohort-heatmap-server/package.json b/examples/cohort-heatmap-server/package.json index 99e9d9fa7..c54040a53 100644 --- a/examples/cohort-heatmap-server/package.json +++ b/examples/cohort-heatmap-server/package.json @@ -29,7 +29,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index 6d07144a2..129e2dc9e 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,5 +1,7 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -178,8 +180,8 @@ export function createServer(): McpServer { title: "Get Cohort Retention Data", description: "Returns cohort retention heatmap data showing customer retention over time by signup month", - inputSchema: GetCohortDataInputSchema.shape, - outputSchema: CohortDataSchema.shape, + inputSchema: GetCohortDataInputSchema, + outputSchema: CohortDataSchema, _meta: { ui: { resourceUri } }, }, async ({ metric, periodType, cohortCount, maxPeriods }) => { diff --git a/examples/customer-segmentation-server/main.ts b/examples/customer-segmentation-server/main.ts index 130076c58..c78875777 100644 --- a/examples/customer-segmentation-server/main.ts +++ b/examples/customer-segmentation-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/customer-segmentation-server/package.json b/examples/customer-segmentation-server/package.json index 59e3eb172..ef14b3264 100644 --- a/examples/customer-segmentation-server/package.json +++ b/examples/customer-segmentation-server/package.json @@ -29,7 +29,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index 244415d1f..7cc1c1fe2 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,8 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -100,8 +100,8 @@ export function createServer(): McpServer { title: "Get Customer Data", description: "Returns customer data with segment information for visualization. Optionally filter by segment.", - inputSchema: GetCustomerDataInputSchema.shape, - outputSchema: GetCustomerDataOutputSchema.shape, + inputSchema: GetCustomerDataInputSchema, + outputSchema: GetCustomerDataOutputSchema, _meta: { ui: { resourceUri } }, }, async ({ segment }): Promise => { diff --git a/examples/debug-server/main.ts b/examples/debug-server/main.ts index 3c89a975b..9fb0825f8 100644 --- a/examples/debug-server/main.ts +++ b/examples/debug-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/debug-server/package.json b/examples/debug-server/package.json index 02e9fbf70..0e3f508f7 100644 --- a/examples/debug-server/package.json +++ b/examples/debug-server/package.json @@ -37,7 +37,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "zod": "^4.1.13" }, "devDependencies": { diff --git a/examples/debug-server/server.ts b/examples/debug-server/server.ts index fd3452a19..068f94da3 100644 --- a/examples/debug-server/server.ts +++ b/examples/debug-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import { appendFileSync } from "node:fs"; import path from "node:path"; diff --git a/examples/integration-server/main.ts b/examples/integration-server/main.ts index c45787e2b..9a42842df 100644 --- a/examples/integration-server/main.ts +++ b/examples/integration-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/integration-server/package.json b/examples/integration-server/package.json index 3f9d8bb14..56fc8eda6 100644 --- a/examples/integration-server/package.json +++ b/examples/integration-server/package.json @@ -15,10 +15,11 @@ "serve": "bun --watch main.ts" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", diff --git a/examples/integration-server/server.ts b/examples/integration-server/server.ts index e44689d24..346f4f7a8 100644 --- a/examples/integration-server/server.ts +++ b/examples/integration-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -33,7 +33,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: {}, + inputSchema: z.object({}), outputSchema: z.object({ time: z.string(), }), @@ -72,7 +72,7 @@ export function createServer(): McpServer { ); // Sample downloadable resource — used to demo ResourceLink in ui/download-file - server.resource( + server.registerResource( SAMPLE_DOWNLOAD_URI, SAMPLE_DOWNLOAD_URI, { diff --git a/examples/integration-server/src/mcp-app.tsx b/examples/integration-server/src/mcp-app.tsx index 7f0b18c27..281a35674 100644 --- a/examples/integration-server/src/mcp-app.tsx +++ b/examples/integration-server/src/mcp-app.tsx @@ -3,7 +3,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { StrictMode, useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import styles from "./mcp-app.module.css"; diff --git a/examples/lazy-auth-server/package.json b/examples/lazy-auth-server/package.json index 086f20a82..6aef3e12f 100644 --- a/examples/lazy-auth-server/package.json +++ b/examples/lazy-auth-server/package.json @@ -24,10 +24,11 @@ "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", - "jose": "^6.0.0" + "jose": "^6.0.0", + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/lazy-auth-server/server.ts b/examples/lazy-auth-server/server.ts index fe7b1acb5..01535ebfd 100644 --- a/examples/lazy-auth-server/server.ts +++ b/examples/lazy-auth-server/server.ts @@ -21,13 +21,13 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { UrlElicitationRequiredError } from "@modelcontextprotocol/sdk/types.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { + McpServer, + UrlElicitationRequiredError, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import cors from "cors"; import express, { type Express, type Request, type Response } from "express"; import { SignJWT, jwtVerify } from "jose"; @@ -35,6 +35,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js). Derived // from import.meta.url rather than import.meta.filename/dirname, which are @@ -605,7 +606,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Show Auth Button", description: "Public tool: shows a button that triggers the protected get_secret tool.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri: buttonUri } }, }, async (): Promise => ({ @@ -638,7 +639,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Get Secret", description: "Protected tool: returns secret data. Requires authentication.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri: secretUri } }, }, async (): Promise => { @@ -682,7 +683,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Revoke Auth Token", description: "Protected tool: revokes the caller's entire auth session (access + refresh token).", - inputSchema: {}, + inputSchema: z.object({}), }, async (): Promise => { if (!authInfo) { @@ -720,7 +721,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Elicit URL (session-callback)", description: "URL elicitation via elicitInput. Blocks until the client accepts; sends ElicitCompleteNotification before returning.", - inputSchema: {}, + inputSchema: z.object({}), }, async (_args, extra): Promise => { const eid = crypto.randomUUID(); @@ -735,7 +736,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { message: "Please open this URL to continue.", elicitationId: eid, }, - { relatedRequestId: extra.requestId, timeout: 300_000 }, + { relatedRequestId: extra.mcpReq.id, timeout: 300_000 }, ); // Send completion notification before returning — lands on the same SSE // stream as this tool response. For Streamable HTTP this is the ONLY @@ -767,7 +768,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Elicit by -32042 Error", description: "Throws UrlElicitationRequiredError (-32042). Client must open the URL, then retry. On retry, if the last-issued elicitationId was completed, sends ElicitCompleteNotification and succeeds.", - inputSchema: {}, + inputSchema: z.object({}), }, async (): Promise => { // Retry path: check if the user completed the elicitation we last issued. @@ -1031,7 +1032,7 @@ export function createApp(): Express { } const server = createServer(authInfo, req); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); res.on("close", () => { diff --git a/examples/map-server/main.ts b/examples/map-server/main.ts index f91f57b6e..80383942d 100644 --- a/examples/map-server/main.ts +++ b/examples/map-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/map-server/package.json b/examples/map-server/package.json index b0c629ace..a3639ce5e 100644 --- a/examples/map-server/package.json +++ b/examples/map-server/package.json @@ -26,10 +26,11 @@ "serve": "bun --watch main.ts" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" diff --git a/examples/map-server/server.ts b/examples/map-server/server.ts index 10125a641..5767d46ba 100644 --- a/examples/map-server/server.ts +++ b/examples/map-server/server.ts @@ -5,11 +5,11 @@ * - geocode: Search for places using OpenStreetMap Nominatim * - show-map: Display an interactive 3D globe at a given location */ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -148,7 +148,7 @@ export function createServer(): McpServer { title: "Show Map", description: "Display an interactive world map zoomed to a specific bounding box. Use the GeoCode tool to find the bounding box of a location. The widget is interactive and exposes tools for navigation (fly to locations) and querying the current view.", - inputSchema: { + inputSchema: z.object({ west: z .number() .optional() @@ -173,7 +173,7 @@ export function createServer(): McpServer { .string() .optional() .describe("Optional label to display on the map"), - }, + }), _meta: { ui: { resourceUri } }, }, async ({ west, south, east, north, label }): Promise => ({ @@ -196,13 +196,13 @@ export function createServer(): McpServer { title: "Geocode", description: "Search for places using OpenStreetMap. Returns coordinates and bounding boxes for up to 5 matches.", - inputSchema: { + inputSchema: z.object({ query: z .string() .describe( "Place name or address to search for (e.g., 'Paris', 'Golden Gate Bridge', '1600 Pennsylvania Ave')", ), - }, + }), }, async ({ query }): Promise => { try { diff --git a/examples/map-server/src/mcp-app.ts b/examples/map-server/src/mcp-app.ts index 39d7c2a22..d3090c7a4 100644 --- a/examples/map-server/src/mcp-app.ts +++ b/examples/map-server/src/mcp-app.ts @@ -6,7 +6,7 @@ * a navigate-to tool for the host to control navigation. */ import { App } from "@modelcontextprotocol/ext-apps"; -import type { ContentBlock } from "@modelcontextprotocol/sdk/spec.types.js"; +import type { ContentBlock } from "@modelcontextprotocol/client"; import { z } from "zod"; // TypeScript declaration for Cesium loaded from CDN diff --git a/examples/pdf-server/main.ts b/examples/pdf-server/main.ts index 1ff848a3e..bb7b57f42 100644 --- a/examples/pdf-server/main.ts +++ b/examples/pdf-server/main.ts @@ -6,10 +6,10 @@ import fs from "node:fs"; import path from "node:path"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { @@ -39,7 +39,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/pdf-server/package.json b/examples/pdf-server/package.json index aeb52c770..a7eb4bb95 100644 --- a/examples/pdf-server/package.json +++ b/examples/pdf-server/package.json @@ -26,10 +26,11 @@ }, "dependencies": { "@cantoo/pdf-lib": "^2.6.5", + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "pdfjs-dist": "^5.0.0", diff --git a/examples/pdf-server/server.test.ts b/examples/pdf-server/server.test.ts index 33a03f1fd..0b2a5eb4d 100644 --- a/examples/pdf-server/server.test.ts +++ b/examples/pdf-server/server.test.ts @@ -2,8 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; import { PDFDocument } from "@cantoo/pdf-lib"; import { makeRandomJpeg } from "../../tests/helpers/range-counting-server"; diff --git a/examples/pdf-server/server.ts b/examples/pdf-server/server.ts index 93dedfdc9..e67cd4731 100644 --- a/examples/pdf-server/server.ts +++ b/examples/pdf-server/server.ts @@ -13,18 +13,19 @@ import { randomUUID } from "crypto"; import fs from "node:fs"; import path from "node:path"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; import { - RootsListChangedNotificationSchema, + McpServer, type CallToolResult, + type ElicitResult, + type PrimitiveSchemaDefinition, type ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; + type Server, +} from "@modelcontextprotocol/server"; // Stub DOMMatrix/ImageData/Path2D before pdfjs-dist loads — its legacy // build instantiates DOMMatrix at module scope and the @napi-rs/canvas // polyfill is unreliable under npx. See ./pdfjs-polyfill.ts for details. @@ -62,10 +63,6 @@ class FetchStandardFontDataFactory { return new Uint8Array(await res.arrayBuffer()); } } -import type { - PrimitiveSchemaDefinition, - ElicitResult, -} from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; // ============================================================================= @@ -1282,7 +1279,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { refreshRoots(server.server); }; server.server.setNotificationHandler( - RootsListChangedNotificationSchema, + "notifications/roots/list_changed", async () => { await refreshRoots(server.server); }, @@ -1292,10 +1289,12 @@ export function createServer(options: CreateServerOptions = {}): McpServer { const { readPdfRange } = sharedPdfCache; // Tool: list_pdfs - List available PDFs - server.tool( + server.registerTool( "list_pdfs", - "List available PDFs that can be displayed", - {}, + { + description: "List available PDFs that can be displayed", + inputSchema: z.object({}), + }, async (): Promise => { const seen = new Set(); const localFiles: string[] = []; @@ -1377,7 +1376,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { title: "Read PDF Bytes", description: "Read a range of bytes from a PDF (max 512KB per request). The model should NOT call this tool directly.", - inputSchema: { + inputSchema: z.object({ url: z.string().describe("PDF URL or local file path"), offset: z.number().min(0).default(0).describe("Byte offset"), byteCount: z @@ -1386,7 +1385,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { .max(MAX_CHUNK_BYTES) .default(MAX_CHUNK_BYTES) .describe("Bytes to read"), - }, + }), outputSchema: z.object({ url: z.string(), bytes: z.string().describe("Base64 encoded bytes"), @@ -1473,7 +1472,7 @@ Returns a viewUUID in structuredContent. Pass it to \`interact\`: Accepts local files (use list_pdfs), client MCP root directories, or any HTTPS URL. Set \`elicit_form_inputs\` to true to prompt the user to fill form fields before display.`, - inputSchema: { + inputSchema: z.object({ url: z .string() .default(DEFAULT_PDF) @@ -1489,7 +1488,7 @@ Set \`elicit_form_inputs\` to true to prompt the user to fill form fields before "If true and the PDF has form fields, prompt the user to fill them before displaying", ), }), - }, + }), outputSchema: z.object({ viewUUID: z .string() @@ -2426,7 +2425,7 @@ Example — add a signature image and a stamp, then screenshot to verify: **FORMS** — fill_form: fill fields with \`fields\` array of {name, value}. **SAVE** — save_as: write the annotated PDF (annotations + form values) to a file. Pass \`path\` (absolute path or file://) for a new location, or omit \`path\` to overwrite the original. Set \`overwrite: true\` to replace an existing file (always required when omitting \`path\`).`, - inputSchema: { + inputSchema: z.object({ viewUUID: z .string() .describe( @@ -2523,7 +2522,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .describe( "Array of commands to execute sequentially. More efficient than separate calls. Tip: end with get_pages+getScreenshots to verify changes.", ), - }, + }), }, async ( { @@ -2600,7 +2599,7 @@ Example — add a signature image and a stamp, then screenshot to verify: const result = await processInteractCommand( uuid, commandList[i], - extra.signal, + extra.mcpReq.signal, ); if (result.isError) { const errText = result.content @@ -2657,7 +2656,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Page Data", description: "Submit rendered page data for a get_pages request (used by viewer). The model should NOT call this tool directly.", - inputSchema: { + inputSchema: z.object({ requestId: z .string() .describe("The request ID from the get_pages command"), @@ -2670,7 +2669,7 @@ Example — add a signature image and a stamp, then screenshot to verify: }), ) .describe("Page data entries"), - }, + }), _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, pages }): Promise => { @@ -2700,7 +2699,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Save Data", description: "Submit annotated PDF bytes for a save_as request (used by viewer). The model should NOT call this tool directly.", - inputSchema: { + inputSchema: z.object({ requestId: z .string() .describe("The request ID from the save_as command"), @@ -2709,7 +2708,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .string() .optional() .describe("Error message if the viewer failed to build bytes"), - }, + }), _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, data, error }): Promise => { @@ -2739,7 +2738,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Viewer State", description: "Submit a viewer-state snapshot for a get_viewer_state request (used by viewer). The model should NOT call this tool directly.", - inputSchema: { + inputSchema: z.object({ requestId: z .string() .describe("The request ID from the get_viewer_state command"), @@ -2751,7 +2750,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .string() .optional() .describe("Error message if the viewer failed to read state"), - }, + }), _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, state, error }): Promise => { @@ -2781,9 +2780,9 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Poll PDF Commands", description: "Poll for pending commands for a PDF viewer. The model should NOT call this tool directly.", - inputSchema: { + inputSchema: z.object({ viewUUID: z.string().describe("The viewUUID of the PDF viewer"), - }, + }), _meta: { ui: { visibility: ["app"] } }, }, async ({ viewUUID: uuid }): Promise => { @@ -2828,10 +2827,10 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Save PDF", description: "Save annotated PDF bytes back to a local file. The model should NOT call this tool directly — use interact with action: save_as instead.", - inputSchema: { + inputSchema: z.object({ url: z.string().describe("Original PDF URL or local file path"), data: z.string().describe("Base64-encoded PDF bytes"), - }, + }), outputSchema: z.object({ filePath: z.string(), mtimeMs: z.number(), diff --git a/examples/pdf-server/src/mcp-app.ts b/examples/pdf-server/src/mcp-app.ts index a953e242a..45e0c7458 100644 --- a/examples/pdf-server/src/mcp-app.ts +++ b/examples/pdf-server/src/mcp-app.ts @@ -12,8 +12,10 @@ import { applyDocumentTheme, applyHostStyleVariables, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import type { ContentBlock } from "@modelcontextprotocol/sdk/spec.types.js"; +import type { + CallToolResult, + ContentBlock, +} from "@modelcontextprotocol/client"; import { z } from "zod"; import * as pdfjsLib from "pdfjs-dist"; import { AnnotationLayer, AnnotationMode, TextLayer } from "pdfjs-dist"; diff --git a/examples/qr-server/package.json b/examples/qr-server/package.json index a5cbf5b22..cb4e010ab 100644 --- a/examples/qr-server/package.json +++ b/examples/qr-server/package.json @@ -8,7 +8,6 @@ "build": "echo 'No build step needed for Python server'" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0" + "@modelcontextprotocol/ext-apps": "^1.7.0" } } diff --git a/examples/quickstart/main.ts b/examples/quickstart/main.ts index 06895a49a..7f370a868 100644 --- a/examples/quickstart/main.ts +++ b/examples/quickstart/main.ts @@ -1,7 +1,7 @@ -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -21,7 +21,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index 100363786..f741c6e29 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -18,9 +18,10 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", - "express": "^5.1.0" + "express": "^5.1.0", + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/quickstart/server.ts b/examples/quickstart/server.ts index eb3c3b1ab..611d0f072 100644 --- a/examples/quickstart/server.ts +++ b/examples/quickstart/server.ts @@ -3,9 +3,10 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; +import { z } from "zod"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -30,7 +31,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async () => { diff --git a/examples/say-server/package.json b/examples/say-server/package.json index a0076e6b9..2ebc099e6 100644 --- a/examples/say-server/package.json +++ b/examples/say-server/package.json @@ -15,7 +15,6 @@ "build": "echo 'No build step needed for Python server'" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0" + "@modelcontextprotocol/ext-apps": "^1.7.0" } } diff --git a/examples/scenario-modeler-server/main.ts b/examples/scenario-modeler-server/main.ts index dbd3ab6ae..cc20e639a 100644 --- a/examples/scenario-modeler-server/main.ts +++ b/examples/scenario-modeler-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/scenario-modeler-server/package.json b/examples/scenario-modeler-server/package.json index a7e8a9a63..63a5d4871 100644 --- a/examples/scenario-modeler-server/package.json +++ b/examples/scenario-modeler-server/package.json @@ -29,7 +29,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index b59851c40..c5fa05d35 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -305,8 +305,8 @@ export function createServer(): McpServer { title: "Get Scenario Data", description: "Returns SaaS scenario templates and optionally computes custom projections for given inputs", - inputSchema: GetScenarioDataInputSchema.shape, - outputSchema: GetScenarioDataOutputSchema.shape, + inputSchema: GetScenarioDataInputSchema, + outputSchema: GetScenarioDataOutputSchema, _meta: { ui: { resourceUri } }, }, async (args: { diff --git a/examples/shadertoy-server/main.ts b/examples/shadertoy-server/main.ts index ac8f6ad8e..7e388f0a6 100644 --- a/examples/shadertoy-server/main.ts +++ b/examples/shadertoy-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/shadertoy-server/package.json b/examples/shadertoy-server/package.json index 0c275017b..318f01760 100644 --- a/examples/shadertoy-server/package.json +++ b/examples/shadertoy-server/package.json @@ -27,7 +27,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" diff --git a/examples/shadertoy-server/server.ts b/examples/shadertoy-server/server.ts index 5238c40b7..50c27bc66 100644 --- a/examples/shadertoy-server/server.ts +++ b/examples/shadertoy-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; diff --git a/examples/sheet-music-server/main.ts b/examples/sheet-music-server/main.ts index 8b143a7ae..d7bf3a0b6 100644 --- a/examples/sheet-music-server/main.ts +++ b/examples/sheet-music-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/sheet-music-server/package.json b/examples/sheet-music-server/package.json index 6cf175a99..10d7c71bc 100644 --- a/examples/sheet-music-server/package.json +++ b/examples/sheet-music-server/package.json @@ -27,7 +27,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "abcjs": "^6.4.4", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/sheet-music-server/server.ts b/examples/sheet-music-server/server.ts index cfd319b51..be7214ae0 100644 --- a/examples/sheet-music-server/server.ts +++ b/examples/sheet-music-server/server.ts @@ -1,8 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; diff --git a/examples/system-monitor-server/main.ts b/examples/system-monitor-server/main.ts index 365a56046..c55c79973 100644 --- a/examples/system-monitor-server/main.ts +++ b/examples/system-monitor-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/system-monitor-server/package.json b/examples/system-monitor-server/package.json index 760d07fcc..5bc4a9585 100644 --- a/examples/system-monitor-server/package.json +++ b/examples/system-monitor-server/package.json @@ -29,7 +29,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index 8fac5f2f9..a17bcf6c9 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -135,8 +135,8 @@ export function createServer(): McpServer { title: "Get System Info", description: "Returns system information, including hostname, platform, CPU info, and memory.", - inputSchema: {}, - outputSchema: SystemInfoSchema.shape, + inputSchema: z.object({}), + outputSchema: SystemInfoSchema, _meta: { ui: { resourceUri } }, }, (): CallToolResult => { @@ -156,8 +156,8 @@ export function createServer(): McpServer { title: "Poll System Stats", description: "Returns dynamic system metrics for polling: per-core CPU timing, memory usage, and uptime. App-only.", - inputSchema: {}, - outputSchema: PollStatsSchema.shape, + inputSchema: z.object({}), + outputSchema: PollStatsSchema, _meta: { ui: { visibility: ["app"] } }, }, async (): Promise => { diff --git a/examples/threejs-server/main.ts b/examples/threejs-server/main.ts index 13a2084a8..ef16e26e2 100644 --- a/examples/threejs-server/main.ts +++ b/examples/threejs-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/threejs-server/package.json b/examples/threejs-server/package.json index 9dda2886a..fb97d333b 100644 --- a/examples/threejs-server/package.json +++ b/examples/threejs-server/package.json @@ -26,10 +26,11 @@ "serve": "bun --watch main.ts" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index ae320df80..0a13c19ef 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -8,8 +8,10 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -161,7 +163,7 @@ export function createServer(): McpServer { title: "Show Three.js Scene", description: "Render an interactive 3D scene with custom Three.js code. Supports transparent backgrounds (alpha: true) for seamless host UI integration. Available globals: THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass, canvas, width, height.", - inputSchema: { + inputSchema: z.object({ code: z .string() .default(DEFAULT_THREEJS_CODE) @@ -172,7 +174,7 @@ export function createServer(): McpServer { .positive() .default(400) .describe("Height in pixels"), - }, + }), outputSchema: z.object({ success: z.boolean(), }), @@ -192,7 +194,7 @@ export function createServer(): McpServer { { title: "Learn Three.js", description: "Get documentation and examples for using the Three.js View", - inputSchema: {}, + inputSchema: z.object({}), }, async () => { return { diff --git a/examples/threejs-server/src/mcp-app-wrapper.tsx b/examples/threejs-server/src/mcp-app-wrapper.tsx index 34194b15f..44fee0c67 100644 --- a/examples/threejs-server/src/mcp-app-wrapper.tsx +++ b/examples/threejs-server/src/mcp-app-wrapper.tsx @@ -6,7 +6,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { StrictMode, useState, useCallback, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import { z } from "zod"; diff --git a/examples/transcript-server/main.ts b/examples/transcript-server/main.ts index 68db8dc7d..e86ad7c16 100644 --- a/examples/transcript-server/main.ts +++ b/examples/transcript-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -25,7 +25,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/transcript-server/package.json b/examples/transcript-server/package.json index ce5b737e6..eea4a80b5 100644 --- a/examples/transcript-server/package.json +++ b/examples/transcript-server/package.json @@ -27,7 +27,7 @@ "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" diff --git a/examples/transcript-server/server.ts b/examples/transcript-server/server.ts index 56bf88d99..863eb2922 100644 --- a/examples/transcript-server/server.ts +++ b/examples/transcript-server/server.ts @@ -1,8 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { @@ -10,6 +10,7 @@ import { registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; +import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -33,7 +34,7 @@ export function createServer(): McpServer { title: "Transcribe Speech", description: "Opens a live speech transcription interface using the Web Speech API.", - inputSchema: {}, + inputSchema: z.object({}), _meta: { ui: { resourceUri } }, }, async (): Promise => { diff --git a/examples/video-resource-server/main.ts b/examples/video-resource-server/main.ts index 082fa5abc..1983373c6 100644 --- a/examples/video-resource-server/main.ts +++ b/examples/video-resource-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/video-resource-server/package.json b/examples/video-resource-server/package.json index 605b8de11..ea816b93c 100644 --- a/examples/video-resource-server/package.json +++ b/examples/video-resource-server/package.json @@ -24,10 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" diff --git a/examples/video-resource-server/server.ts b/examples/video-resource-server/server.ts index 2cb317e41..8ae624029 100644 --- a/examples/video-resource-server/server.ts +++ b/examples/video-resource-server/server.ts @@ -12,11 +12,9 @@ import { import { McpServer, ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -135,14 +133,14 @@ Available videos: ${Object.entries(VIDEO_LIBRARY) .map(([id, v]) => `- ${id}: ${v.description}`) .join("\n")}`, - inputSchema: { + inputSchema: z.object({ videoId: z .enum(Object.keys(VIDEO_LIBRARY) as [string, ...string[]]) .default("bunny-1mb") .describe( `Video ID to play. Available: ${Object.keys(VIDEO_LIBRARY).join(", ")}`, ), - }, + }), outputSchema: z.object({ videoUri: z.string(), description: z.string(), diff --git a/examples/video-resource-server/src/mcp-app.ts b/examples/video-resource-server/src/mcp-app.ts index 88ed913ee..2a3f8f91d 100644 --- a/examples/video-resource-server/src/mcp-app.ts +++ b/examples/video-resource-server/src/mcp-app.ts @@ -5,7 +5,7 @@ * The video is served as a base64 blob and converted to a data URI for playback. */ import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import "./global.css"; import "./mcp-app.css"; diff --git a/examples/wiki-explorer-server/main.ts b/examples/wiki-explorer-server/main.ts index cc7b3a221..bfeeffbaa 100644 --- a/examples/wiki-explorer-server/main.ts +++ b/examples/wiki-explorer-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { createMcpExpressApp } from "@modelcontextprotocol/express"; +import type { McpServer } from "@modelcontextprotocol/server"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new StreamableHTTPServerTransport({ + const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/wiki-explorer-server/package.json b/examples/wiki-explorer-server/package.json index 62f1de8ad..bd31d6f78 100644 --- a/examples/wiki-explorer-server/package.json +++ b/examples/wiki-explorer-server/package.json @@ -26,10 +26,11 @@ "serve": "bun --watch main.ts" }, "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cheerio": "^1.0.0", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index 59d5e18ee..8bc4eb7cb 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import type { - CallToolResult, - ReadResourceResult, -} from "@modelcontextprotocol/sdk/types.js"; +import { + McpServer, + type CallToolResult, + type ReadResourceResult, +} from "@modelcontextprotocol/server"; import * as cheerio from "cheerio"; import fs from "node:fs/promises"; import path from "node:path"; diff --git a/examples/wiki-explorer-server/src/mcp-app.ts b/examples/wiki-explorer-server/src/mcp-app.ts index 291292dee..9833d9c25 100644 --- a/examples/wiki-explorer-server/src/mcp-app.ts +++ b/examples/wiki-explorer-server/src/mcp-app.ts @@ -2,7 +2,7 @@ * Wiki Explorer - Force-directed graph visualization of Wikipedia link networks */ import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/client"; import { z } from "zod"; import { forceCenter, diff --git a/package-lock.json b/package-lock.json index 5514bf1db..c1ae34d19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@boneskull/typedoc-plugin-mermaid": "^0.2.0", "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/server": "2.0.0-beta.4", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", @@ -55,7 +54,6 @@ "peerDependencies": { "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/server": "2.0.0-beta.4", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", @@ -74,8 +72,8 @@ "name": "@modelcontextprotocol/ext-apps-basic-host", "version": "1.7.4", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0", "react": "^19.2.0", "react-dom": "^19.2.0", "zod": "^4.1.13" @@ -100,10 +98,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "preact": "^10.0.0", @@ -129,10 +128,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", @@ -161,10 +161,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "solid-js": "1.9.10", @@ -190,10 +191,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "svelte": "^5.0.0", @@ -219,10 +221,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" @@ -246,10 +249,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "vue": "^3.5.0", @@ -278,7 +282,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -306,7 +310,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", @@ -338,7 +342,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -366,7 +370,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "zod": "^4.1.13" }, "bin": { @@ -388,10 +392,11 @@ "examples/integration-server": { "version": "1.7.4", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", @@ -421,10 +426,11 @@ "dependencies": { "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", - "jose": "^6.0.0" + "jose": "^6.0.0", + "zod": "^4.1.13" }, "bin": { "mcp-server-lazy-auth": "dist/index.js" @@ -445,10 +451,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" @@ -473,10 +480,11 @@ "license": "MIT", "dependencies": { "@cantoo/pdf-lib": "^2.6.5", + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "pdfjs-dist": "^5.0.0", @@ -500,8 +508,7 @@ "name": "@modelcontextprotocol/server-qr", "version": "1.7.4", "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0" + "@modelcontextprotocol/ext-apps": "^1.7.0" } }, "examples/quickstart": { @@ -512,9 +519,10 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", - "express": "^5.1.0" + "express": "^5.1.0", + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -533,8 +541,7 @@ "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/ext-apps": "^1.7.0", - "@modelcontextprotocol/sdk": "^1.29.0" + "@modelcontextprotocol/ext-apps": "^1.7.0" } }, "examples/scenario-modeler-server": { @@ -545,7 +552,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -578,7 +585,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" @@ -605,7 +612,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "abcjs": "^6.4.4", "cors": "^2.8.5", "express": "^5.1.0", @@ -633,7 +640,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -659,10 +666,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", @@ -696,7 +704,7 @@ "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" @@ -721,10 +729,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cors": "^2.8.5", "express": "^5.1.0", "zod": "^4.1.13" @@ -748,10 +757,11 @@ "version": "1.7.4", "license": "MIT", "dependencies": { + "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/express": "2.0.0-beta.4", "@modelcontextprotocol/ext-apps": "^1.7.0", "@modelcontextprotocol/node": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/server": "2.0.0-beta.4", "cheerio": "^1.0.0", "cors": "^2.8.5", "express": "^5.1.0", @@ -2230,7 +2240,6 @@ "version": "2.0.0-beta.4", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0-beta.4.tgz", "integrity": "sha512-VNHA/UXDk7mCpVl+jOg5B4WMRRD2OEl+it360Lhi6HiCbrIB2f6pZ0cqSDKXQVUtZvqnhdQHzZAM9h8EKWhq/A==", - "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.4", @@ -2336,6 +2345,7 @@ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -3995,6 +4005,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4011,6 +4022,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -5546,6 +5558,7 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", + "peer": true, "dependencies": { "ip-address": "^10.2.0" }, @@ -5563,7 +5576,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-uri": { "version": "3.1.2", @@ -5579,7 +5593,8 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fflate": { "version": "0.8.2", @@ -5906,6 +5921,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -6047,6 +6063,7 @@ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 12" } @@ -6242,13 +6259,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-typed": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -7224,6 +7243,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9350,6 +9370,7 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.25 || ^4" } diff --git a/package.json b/package.json index 35bcdf077..ecbf386a8 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "@boneskull/typedoc-plugin-mermaid": "^0.2.0", "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/server": "2.0.0-beta.4", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", @@ -113,7 +112,6 @@ "peerDependencies": { "@modelcontextprotocol/client": "2.0.0-beta.4", "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/sdk": "^1.29.0", "@modelcontextprotocol/server": "2.0.0-beta.4", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/plugins/mcp-apps/skills/add-app-to-server/SKILL.md b/plugins/mcp-apps/skills/add-app-to-server/SKILL.md index 02d09a4e2..69e41f836 100644 --- a/plugins/mcp-apps/skills/add-app-to-server/SKILL.md +++ b/plugins/mcp-apps/skills/add-app-to-server/SKILL.md @@ -77,13 +77,15 @@ Before writing any code, analyze the server's existing tools and determine which ## Step 2: Add Dependencies ```bash -npm install @modelcontextprotocol/ext-apps +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.4 @modelcontextprotocol/core@2.0.0-beta.4 @modelcontextprotocol/server@2.0.0-beta.4 zod@^4.2.0 npm install -D vite vite-plugin-singlefile ``` Plus framework-specific dependencies if needed (e.g., `react`, `react-dom`, `@vitejs/plugin-react` for React). -Use `npm install` to add dependencies rather than manually writing version numbers. This lets npm resolve the latest compatible versions. Never specify version numbers from memory. +Use the exact base MCP SDK prerelease required by ext-apps. Existing servers +that still import `@modelcontextprotocol/sdk` v1 must migrate those imports and +handler schemas before adding the App integration. ## Step 3: Set Up the Build Pipeline @@ -146,10 +148,14 @@ Transform plain MCP tools into App tools with UI. **Before** (plain MCP tool): ```typescript -server.tool("my-tool", { param: z.string() }, async (args) => { - const data = await fetchData(args.param); - return { content: [{ type: "text", text: JSON.stringify(data) }] }; -}); +server.registerTool( + "my-tool", + { inputSchema: z.object({ param: z.string() }) }, + async ({ param }) => { + const data = await fetchData(param); + return { content: [{ type: "text", text: JSON.stringify(data) }] }; + }, +); ``` **After** (App tool with UI): @@ -158,17 +164,22 @@ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@model const resourceUri = "ui://my-tool/mcp-app.html"; -registerAppTool(server, "my-tool", { - description: "Shows data with an interactive UI", - inputSchema: { param: z.string() }, - _meta: { ui: { resourceUri } }, -}, async (args) => { - const data = await fetchData(args.param); - return { - content: [{ type: "text", text: JSON.stringify(data) }], // text fallback for non-UI hosts - structuredContent: { data }, // structured data for the UI - }; -}); +registerAppTool( + server, + "my-tool", + { + description: "Shows data with an interactive UI", + inputSchema: z.object({ param: z.string() }), + _meta: { ui: { resourceUri } }, + }, + async ({ param }) => { + const data = await fetchData(param); + return { + content: [{ type: "text", text: JSON.stringify(data) }], // text fallback for non-UI hosts + structuredContent: { data }, // structured data for the UI + }; + }, +); ``` Key guidance: @@ -187,17 +198,21 @@ import path from "node:path"; const resourceUri = "ui://my-tool/mcp-app.html"; -registerAppResource(server, { - uri: resourceUri, - name: "My Tool UI", - mimeType: RESOURCE_MIME_TYPE, -}, async () => { - const html = await fs.readFile( - path.resolve(import.meta.dirname, "dist", "mcp-app.html"), - "utf-8", - ); - return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] }; -}); +registerAppResource( + server, + "My Tool UI", + resourceUri, + {}, + async () => { + const html = await fs.readFile( + path.resolve(import.meta.dirname, "dist", "mcp-app.html"), + "utf-8", + ); + return { + contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }], + }; + }, +); ``` If multiple tools share the same UI, they can reference the same `resourceUri` and the same resource registration. @@ -271,25 +286,32 @@ registerAppTool(server, "poll-data", { }); ``` -The UI calls these via `app.callServerTool("poll-data", {})`. +The UI calls these via +`app.callServerTool({ name: "poll-data", arguments: {} })`. ### CSP Configuration If the UI needs to load external resources (fonts, APIs, CDNs), declare the domains: ```typescript -registerAppResource(server, { - uri: resourceUri, - name: "My Tool UI", - mimeType: RESOURCE_MIME_TYPE, - _meta: { - ui: { - connectDomains: ["api.example.com"], // fetch/XHR targets - resourceDomains: ["cdn.example.com"], // scripts, styles, images - frameDomains: ["embed.example.com"], // nested iframes +registerAppResource(server, "My Tool UI", resourceUri, {}, async () => ({ + contents: [ + { + uri: resourceUri, + mimeType: RESOURCE_MIME_TYPE, + text: html, + _meta: { + ui: { + csp: { + connectDomains: ["https://api.example.com"], // fetch/XHR targets + resourceDomains: ["https://cdn.example.com"], // scripts, styles, images + frameDomains: ["https://embed.example.com"], // nested iframes + }, + }, + }, }, - }, -}, async () => { /* ... */ }); + ], +})); ``` ### Streaming Partial Input @@ -326,7 +348,14 @@ server.server.oninitialized = () => { }, appToolHandler); } else { // Text-only client — register plain tool - server.tool("my-tool", "Shows data", { param: z.string() }, plainToolHandler); + server.registerTool( + "my-tool", + { + description: "Shows data", + inputSchema: z.object({ param: z.string() }), + }, + plainToolHandler, + ); } }; ``` diff --git a/plugins/mcp-apps/skills/convert-web-app/SKILL.md b/plugins/mcp-apps/skills/convert-web-app/SKILL.md index 5db2d946c..22686ef5e 100644 --- a/plugins/mcp-apps/skills/convert-web-app/SKILL.md +++ b/plugins/mcp-apps/skills/convert-web-app/SKILL.md @@ -115,19 +115,20 @@ Create a new MCP server with tool and resource registration. This wraps the exis ### Dependencies ```bash -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.4 @modelcontextprotocol/core@2.0.0-beta.4 @modelcontextprotocol/server@2.0.0-beta.4 zod@^4.2.0 npm install -D tsx vite vite-plugin-singlefile ``` -Use `npm install` to add dependencies rather than manually writing version numbers. This lets npm resolve the latest compatible versions. Never specify version numbers from memory. +Use `npm install` with the exact base MCP SDK prerelease required by ext-apps. +Do not substitute unpublished local packages or guess a different prerelease. ### Server Code Create `server.ts`: ```typescript -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { McpServer } from "@modelcontextprotocol/server"; +import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; import fs from "node:fs/promises"; import path from "node:path"; @@ -140,7 +141,9 @@ const resourceUri = "ui://my-app/mcp-app.html"; // Register the tool — inputSchema maps to the app's data sources registerAppTool(server, "show-app", { description: "Displays the app with the given parameters", - inputSchema: { query: z.string().describe("The search query") }, + inputSchema: z.object({ + query: z.string().describe("The search query"), + }), _meta: { ui: { resourceUri } }, }, async (args) => { // Process args server-side if needed @@ -151,19 +154,29 @@ registerAppTool(server, "show-app", { }); // Register the HTML resource -registerAppResource(server, { - uri: resourceUri, - name: "My App UI", - mimeType: RESOURCE_MIME_TYPE, - // Add CSP domains from Step 2 if needed: - // _meta: { ui: { connectDomains: ["api.example.com"], resourceDomains: ["cdn.example.com"] } }, -}, async () => { - const html = await fs.readFile( - path.resolve(import.meta.dirname, "dist", "mcp-app.html"), - "utf-8", - ); - return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] }; -}); +registerAppResource( + server, + "My App UI", + resourceUri, + {}, + async () => { + const html = await fs.readFile( + path.resolve(import.meta.dirname, "dist", "mcp-app.html"), + "utf-8", + ); + return { + contents: [ + { + uri: resourceUri, + mimeType: RESOURCE_MIME_TYPE, + text: html, + // Add CSP domains from Step 2 if needed: + // _meta: { ui: { csp: { connectDomains: ["https://api.example.com"], resourceDomains: ["https://cdn.example.com"] } } }, + }, + ], + }; + }, +); // Start the server const transport = new StdioServerTransport(); @@ -310,7 +323,10 @@ const data = await fetch("/api/data").then(r => r.json()); // After (hybrid): async function fetchData(): Promise { if (isMcpApp) { - const result = await app.callServerTool("fetch-data", {}); + const result = await app.callServerTool({ + name: "fetch-data", + arguments: {}, + }); return result.structuredContent; } return fetch("/api/data").then(r => r.json()); @@ -437,7 +453,8 @@ registerAppTool(server, "refresh-data", { }); ``` -The UI calls these via `app.callServerTool("refresh-data", {})`. +The UI calls these via +`app.callServerTool({ name: "refresh-data", arguments: {} })`. ### Streaming Partial Input diff --git a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md index 5bd12191d..fc41e70cd 100644 --- a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md +++ b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md @@ -110,11 +110,12 @@ See `/tmp/mcp-ext-apps/docs/patterns.md` for detailed recipes: **Always** use `npm install` to add dependencies rather than manually writing version numbers: ```bash -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod express cors +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.4 @modelcontextprotocol/core@2.0.0-beta.4 @modelcontextprotocol/server@2.0.0-beta.4 @modelcontextprotocol/node@2.0.0-beta.4 @modelcontextprotocol/express@2.0.0-beta.4 zod@^4.2.0 express cors npm install -D typescript vite vite-plugin-singlefile concurrently cross-env @types/node @types/express @types/cors ``` -This lets npm resolve the latest compatible versions. **Never** specify version numbers from memory. +Use the exact base MCP SDK prerelease required by ext-apps. Do not substitute +unpublished local packages or guess a different prerelease. ### TypeScript Server Execution diff --git a/scripts/typedoc-plugin-base-sdk-links.mjs b/scripts/typedoc-plugin-base-sdk-links.mjs new file mode 100644 index 000000000..609891828 --- /dev/null +++ b/scripts/typedoc-plugin-base-sdk-links.mjs @@ -0,0 +1,16 @@ +/** + * Resolve module-local links inherited from the base MCP SDK declarations. + * + * TypeDoc's externalSymbolLinkMappings handles package-qualified symbols, but + * the base SDK writes this particular reference as `index.inputRequired`. + */ + +const BASE_SDK_DOCS = "https://ts.sdk.modelcontextprotocol.io/v2/"; + +/** @param {import("typedoc").Application} app */ +export function load(app) { + app.converter.addUnknownSymbolResolver((ref) => { + const name = ref.symbolReference?.path?.map((part) => part.path).join("."); + if (name === "index.inputRequired") return BASE_SDK_DOCS; + }); +} diff --git a/src/app-bridge.examples.ts b/src/app-bridge.examples.ts index d5dc5bc74..47ad6234f 100644 --- a/src/app-bridge.examples.ts +++ b/src/app-bridge.examples.ts @@ -7,17 +7,11 @@ * @module */ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; -import { - CallToolResult, - CallToolResultSchema, +import { Client, type Transport } from "@modelcontextprotocol/client"; +import type { CreateMessageRequest, CreateMessageResult, - ListResourcesResultSchema, - ReadResourceResultSchema, - ListPromptsResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; +} from "@modelcontextprotocol/server"; import { AppBridge, PostMessageTransport } from "./app-bridge.js"; import type { McpUiDisplayMode } from "./types.js"; @@ -223,8 +217,7 @@ function AppBridge_oncalltool_forwardToServer( bridge.oncalltool = async (params, extra) => { return mcpClient.request( { method: "tools/call", params }, - CallToolResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; //#endregion AppBridge_oncalltool_forwardToServer @@ -245,7 +238,9 @@ function AppBridge_oncreatesamplingmessage_forwardToLlm( //#region AppBridge_oncreatesamplingmessage_forwardToLlm bridge.oncreatesamplingmessage = async (params, extra) => { // Apply rate limiting, user approval, cost controls here - return await myLlmProvider.complete(params, { signal: extra.signal }); + return await myLlmProvider.complete(params, { + signal: extra.mcpReq.signal, + }); }; //#endregion AppBridge_oncreatesamplingmessage_forwardToLlm } @@ -261,8 +256,7 @@ function AppBridge_onlistresources_returnResources( bridge.onlistresources = async (params, extra) => { return mcpClient.request( { method: "resources/list", params }, - ListResourcesResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; //#endregion AppBridge_onlistresources_returnResources @@ -279,8 +273,7 @@ function AppBridge_onreadresource_returnResource( bridge.onreadresource = async (params, extra) => { return mcpClient.request( { method: "resources/read", params }, - ReadResourceResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; //#endregion AppBridge_onreadresource_returnResource @@ -297,8 +290,7 @@ function AppBridge_onlistprompts_returnPrompts( bridge.onlistprompts = async (params, extra) => { return mcpClient.request( { method: "prompts/list", params }, - ListPromptsResultSchema, - { signal: extra.signal }, + { signal: extra.mcpReq.signal }, ); }; //#endregion AppBridge_onlistprompts_returnPrompts @@ -444,10 +436,10 @@ async function AppBridge_sendToolResult_afterExecution( args: Record, ) { //#region AppBridge_sendToolResult_afterExecution - const result = await mcpClient.request( - { method: "tools/call", params: { name: "get_weather", arguments: args } }, - CallToolResultSchema, - ); + const result = await mcpClient.request({ + method: "tools/call", + params: { name: "get_weather", arguments: args }, + }); bridge.sendToolResult(result); //#endregion AppBridge_sendToolResult_afterExecution } diff --git a/src/app-bridge.ts b/src/app-bridge.ts index c65510d39..d353cf71e 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -261,7 +261,7 @@ export type AppBridgeEventMap = { /** * Host-side bridge for communicating with a single View ({@link app!App `App`}). * - * `AppBridge` extends the MCP SDK's `Protocol` class and acts as a proxy between + * `AppBridge` extends the base MCP SDK's public `Server` class and acts as a proxy between * the host application and a view running in an iframe. When an MCP client * is provided to the constructor, it automatically forwards MCP server capabilities * (tools, resources, prompts) to the view. It also handles the initialization @@ -667,8 +667,7 @@ export class AppBridge extends Server { * ```typescript * bridge.onsandboxready = async () => { * const resource = await mcpClient.request( - * { method: "resources/read", params: { uri: "ui://my-app" } }, - * ReadResourceResultSchema + * { method: "resources/read", params: { uri: "ui://my-app" } } * ); * * bridge.sendSandboxResourceReady({ @@ -1154,14 +1153,13 @@ export class AppBridge extends Server { * bridge.oncalltool = async (params, extra) => { * return mcpClient.request( * { method: "tools/call", params }, - * CallToolResultSchema, - * { signal: extra.signal }, + * { signal: extra.mcpReq.signal }, * ); * }; * ``` * - * @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type - * @see `CallToolResult` from @modelcontextprotocol/sdk for the result type + * @see `CallToolRequest` from @modelcontextprotocol/server for the request type + * @see `CallToolResult` from @modelcontextprotocol/server for the result type */ private _oncalltool?: ( params: CallToolRequest["params"], @@ -1209,12 +1207,14 @@ export class AppBridge extends Server { * ```ts source="./app-bridge.examples.ts#AppBridge_oncreatesamplingmessage_forwardToLlm" * bridge.oncreatesamplingmessage = async (params, extra) => { * // Apply rate limiting, user approval, cost controls here - * return await myLlmProvider.complete(params, { signal: extra.signal }); + * return await myLlmProvider.complete(params, { + * signal: extra.mcpReq.signal, + * }); * }; * ``` * - * @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type - * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types + * @see `CreateMessageRequest` from @modelcontextprotocol/server for the request type + * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/server for result types */ set oncreatesamplingmessage( callback: ( @@ -1244,7 +1244,7 @@ export class AppBridge extends Server { * }); * ``` * - * @see `ToolListChangedNotification` from @modelcontextprotocol/sdk for the notification type + * @see `ToolListChangedNotification` from @modelcontextprotocol/server for the notification type */ sendToolListChanged(params: ToolListChangedNotification["params"] = {}) { return this.notification({ @@ -1270,14 +1270,13 @@ export class AppBridge extends Server { * bridge.onlistresources = async (params, extra) => { * return mcpClient.request( * { method: "resources/list", params }, - * ListResourcesResultSchema, - * { signal: extra.signal }, + * { signal: extra.mcpReq.signal }, * ); * }; * ``` * - * @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type - * @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type + * @see `ListResourcesRequest` from @modelcontextprotocol/server for the request type + * @see `ListResourcesResult` from @modelcontextprotocol/server for the result type */ private _onlistresources?: ( params: ListResourcesRequest["params"], @@ -1325,14 +1324,13 @@ export class AppBridge extends Server { * bridge.onlistresourcetemplates = async (params, extra) => { * return mcpClient.request( * { method: "resources/templates/list", params }, - * ListResourceTemplatesResultSchema, - * { signal: extra.signal } + * { signal: extra.mcpReq.signal } * ); * }; * ``` * - * @see `ListResourceTemplatesRequest` from @modelcontextprotocol/sdk for the request type - * @see `ListResourceTemplatesResult` from @modelcontextprotocol/sdk for the result type + * @see `ListResourceTemplatesRequest` from @modelcontextprotocol/server for the request type + * @see `ListResourceTemplatesResult` from @modelcontextprotocol/server for the result type */ private _onlistresourcetemplates?: ( params: ListResourceTemplatesRequest["params"], @@ -1383,14 +1381,13 @@ export class AppBridge extends Server { * bridge.onreadresource = async (params, extra) => { * return mcpClient.request( * { method: "resources/read", params }, - * ReadResourceResultSchema, - * { signal: extra.signal }, + * { signal: extra.mcpReq.signal }, * ); * }; * ``` * - * @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type - * @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type + * @see `ReadResourceRequest` from @modelcontextprotocol/server for the request type + * @see `ReadResourceResult` from @modelcontextprotocol/server for the result type */ private _onreadresource?: ( params: ReadResourceRequest["params"], @@ -1438,7 +1435,7 @@ export class AppBridge extends Server { * }); * ``` * - * @see `ResourceListChangedNotification` from @modelcontextprotocol/sdk for the notification type + * @see `ResourceListChangedNotification` from @modelcontextprotocol/server for the notification type */ sendResourceListChanged( params: ResourceListChangedNotification["params"] = {}, @@ -1466,14 +1463,13 @@ export class AppBridge extends Server { * bridge.onlistprompts = async (params, extra) => { * return mcpClient.request( * { method: "prompts/list", params }, - * ListPromptsResultSchema, - * { signal: extra.signal }, + * { signal: extra.mcpReq.signal }, * ); * }; * ``` * - * @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type - * @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type + * @see `ListPromptsRequest` from @modelcontextprotocol/server for the request type + * @see `ListPromptsResult` from @modelcontextprotocol/server for the result type */ private _onlistprompts?: ( params: ListPromptsRequest["params"], @@ -1520,7 +1516,7 @@ export class AppBridge extends Server { * }); * ``` * - * @see `PromptListChangedNotification` from @modelcontextprotocol/sdk for the notification type + * @see `PromptListChangedNotification` from @modelcontextprotocol/server for the notification type */ sendPromptListChanged(params: PromptListChangedNotification["params"] = {}) { return this.notification({ @@ -1722,10 +1718,10 @@ export class AppBridge extends Server { * * @example * ```ts source="./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution" - * const result = await mcpClient.request( - * { method: "tools/call", params: { name: "get_weather", arguments: args } }, - * CallToolResultSchema, - * ); + * const result = await mcpClient.request({ + * method: "tools/call", + * params: { name: "get_weather", arguments: args }, + * }); * bridge.sendToolResult(result); * ``` * diff --git a/src/app.examples.ts b/src/app.examples.ts index 81bdd4440..7d475efa0 100644 --- a/src/app.examples.ts +++ b/src/app.examples.ts @@ -7,11 +7,8 @@ * @module */ -import type { Tool } from "@modelcontextprotocol/sdk/types.js"; -import type { - McpServer, - ToolCallback, -} from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { SamplingMessage, Tool } from "@modelcontextprotocol/client"; +import type { McpServer, ToolCallback } from "@modelcontextprotocol/server"; import { App, PostMessageTransport, @@ -369,7 +366,7 @@ async function App_createSamplingMessage_simple(app: App) { */ async function App_createSamplingMessage_withTools( app: App, - messages: import("@modelcontextprotocol/sdk/types.js").SamplingMessage[], + messages: SamplingMessage[], ) { //#region App_createSamplingMessage_withTools if (!app.getHostCapabilities()?.sampling?.tools) return; diff --git a/src/app.ts b/src/app.ts index 3c8f5b79e..7e752cec3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -174,9 +174,9 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; /** * Options for configuring {@link App `App`} behavior. * - * Extends `ProtocolOptions` from the MCP SDK with `App`-specific configuration. + * Extends `ClientOptions` from the base MCP SDK with `App`-specific configuration. * - * @see `ProtocolOptions` from @modelcontextprotocol/sdk for inherited options + * @see `ClientOptions` from @modelcontextprotocol/client for inherited options */ export type AppOptions = Omit< ClientOptions, @@ -243,7 +243,7 @@ export type AppToolResult< * validated/parsed input; when `In` is `undefined`, the callback receives only * `extra`. When `Out` is provided, the return's `structuredContent` is typed. * - * Mirrors `ToolCallback` from `@modelcontextprotocol/sdk/server/mcp.js` but is + * Mirrors `ToolCallback` from `@modelcontextprotocol/server` but is * parameterized over {@link StandardSchemaV1} instead of zod, so any * Standard-Schema-compatible library (Zod, ArkType, Valibot, …) can be used. */ @@ -261,7 +261,7 @@ export type AppToolCallback< /** * Handle returned by {@link App.registerTool}. Mirrors `RegisteredTool` from - * `@modelcontextprotocol/sdk/server/mcp.js` but stores + * `@modelcontextprotocol/server` but stores * {@link StandardSchemaV1} schemas. */ export type RegisteredAppTool = { @@ -302,9 +302,8 @@ export type AppEventMap = { * Main class for MCP Apps to communicate with their host. * * The `App` class provides a framework-agnostic way to build interactive MCP Apps - * that run inside host applications. It extends the MCP SDK's `Protocol` class and - * handles the connection lifecycle, initialization handshake, and bidirectional - * communication with the host. + * that run inside host applications. It extends the base MCP SDK's public + * `Client` class and adds the Apps handshake, lifecycle, and high-level APIs. * * ## Architecture * @@ -321,7 +320,7 @@ export type AppEventMap = { * * ## Protocol and event methods * - * As a subclass of the MCP SDK's `Protocol`, `App` provides: + * As a subclass of the base MCP SDK's `Client`, `App` provides: * - `setRequestHandler()` - Register handlers for requests from host * - `setNotificationHandler()` - Register handlers for notifications from host * - `addEventListener()` - Append a listener for a notification event (multi-listener) @@ -1458,8 +1457,8 @@ export class App extends Client { * } * ``` * - * @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type - * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types + * @see `CreateMessageRequest` from @modelcontextprotocol/client for the request type + * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/client for result types */ async createSamplingMessage( params: CreateMessageRequest["params"] & { tools?: undefined }, diff --git a/src/events.ts b/src/events.ts index b89186713..aff144605 100644 --- a/src/events.ts +++ b/src/events.ts @@ -38,9 +38,8 @@ export class EventDispatcher> { getHandler( event: K, ): ((params: EventMap[K]) => void) | undefined { - return (this._eventSlots.get(event) as - | EventSlot - | undefined)?.onHandler; + return (this._eventSlots.get(event) as EventSlot | undefined) + ?.onHandler; } /** Add a listener. Listeners run in insertion order after the handler. */ diff --git a/src/message-transport.test.ts b/src/message-transport.test.ts index d5f509204..2a115f3f9 100644 --- a/src/message-transport.test.ts +++ b/src/message-transport.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; -import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; +import type { JSONRPCMessage } from "@modelcontextprotocol/client"; import { PostMessageTransport } from "./message-transport"; diff --git a/src/server/index.examples.ts b/src/server/index.examples.ts index d583ae85c..9ceebcebb 100644 --- a/src/server/index.examples.ts +++ b/src/server/index.examples.ts @@ -9,17 +9,14 @@ import * as crypto from "node:crypto"; import * as fs from "node:fs/promises"; -import type { - McpServer, - ToolCallback, - ReadResourceCallback, -} from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { McpServer, ToolCallback } from "@modelcontextprotocol/server"; import { z } from "zod"; import { registerAppTool, registerAppResource, getUiCapability, RESOURCE_MIME_TYPE, + type ReadResourceCallback, } from "./index.js"; // Stubs for external functions used in examples @@ -74,7 +71,7 @@ function registerAppTool_basicUsage(server: McpServer) { { title: "Get Weather", description: "Get current weather for a location", - inputSchema: { location: z.string() }, + inputSchema: z.object({ location: z.string() }), _meta: { ui: { resourceUri: "ui://weather/view.html" }, }, @@ -122,7 +119,7 @@ function registerAppTool_appOnlyVisibility(server: McpServer) { "update-quantity", { description: "Update item quantity in cart", - inputSchema: { itemId: z.string(), quantity: z.number() }, + inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), _meta: { ui: { resourceUri: "ui://shop/cart.html", diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 1853cc05a..5de341f9e 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -7,7 +7,7 @@ import { getUiCapability, EXTENSION_ID, } from "./index"; -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { McpServer } from "@modelcontextprotocol/server"; describe("registerAppTool", () => { it("should pass through config to server.registerTool", () => { diff --git a/src/server/index.ts b/src/server/index.ts index b8b3ad026..61e743831 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -150,7 +150,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * { * title: "Get Weather", * description: "Get current weather for a location", - * inputSchema: { location: z.string() }, + * inputSchema: z.object({ location: z.string() }), * _meta: { * ui: { resourceUri: "ui://weather/view.html" }, * }, @@ -190,7 +190,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * "update-quantity", * { * description: "Update item quantity in cart", - * inputSchema: { itemId: z.string(), quantity: z.number() }, + * inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), * _meta: { * ui: { * resourceUri: "ui://shop/cart.html", diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 00babc403..041bf5e67 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -1,5 +1,7 @@ import { OptionDefaults } from "typedoc"; +const BASE_SDK_DOCS = "https://ts.sdk.modelcontextprotocol.io/v2/"; + /** @type {Partial} */ const config = { name: "MCP Apps", @@ -29,6 +31,28 @@ const config = { excludePrivate: true, excludeInternal: false, intentionallyNotExported: ["RequestHandlerExtra"], + externalSymbolLinkMappings: { + "@modelcontextprotocol/client": { + ClientOptions: BASE_SDK_DOCS, + ConnectOptions: BASE_SDK_DOCS, + DiscoverResult: BASE_SDK_DOCS, + ProtocolError: BASE_SDK_DOCS, + "Protocol.close": BASE_SDK_DOCS, + "Protocol.notification": BASE_SDK_DOCS, + ResponseCacheStore: BASE_SDK_DOCS, + SdkError: BASE_SDK_DOCS, + "SdkErrorCode.MethodNotSupportedByProtocolVersion": BASE_SDK_DOCS, + "__type.enforceStrictCapabilities": BASE_SDK_DOCS, + }, + "@modelcontextprotocol/server": { + LoggingMessageNotification: BASE_SDK_DOCS, + "McpServer.registerTool": BASE_SDK_DOCS, + "MessageExtraInfo.classification": BASE_SDK_DOCS, + "Protocol.close": BASE_SDK_DOCS, + "Protocol.notification": BASE_SDK_DOCS, + "SdkErrorCode.UnsupportedResultType": BASE_SDK_DOCS, + }, + }, blockTags: [...OptionDefaults.blockTags, "@description"], jsDocCompatibility: { exampleTag: false, @@ -49,6 +73,7 @@ const config = { out: "docs/api", plugin: [ "typedoc-github-theme", + "./scripts/typedoc-plugin-base-sdk-links.mjs", "./scripts/typedoc-plugin-fix-mermaid-entities.mjs", "./scripts/typedoc-plugin-seo.mjs", "./scripts/typedoc-plugin-mcpstyle.mjs", From f50ec6ebfbca14bf3d3070b6d01b90f12ba93359 Mon Sep 17 00:00:00 2001 From: Andrew Khadder Date: Tue, 14 Jul 2026 17:16:27 -0700 Subject: [PATCH 7/9] fix: regenerate lockfile for npm 10 --- package-lock.json | 401 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) diff --git a/package-lock.json b/package-lock.json index c1ae34d19..9e0a71b5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-host/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-host/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-preact": { "name": "@modelcontextprotocol/server-basic-preact", "version": "1.7.4", @@ -123,6 +140,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-server-preact/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-preact/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-react": { "name": "@modelcontextprotocol/server-basic-react", "version": "1.7.4", @@ -156,6 +190,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-server-react/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-react/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-solid": { "name": "@modelcontextprotocol/server-basic-solid", "version": "1.7.4", @@ -186,6 +237,23 @@ "vite-plugin-solid": "^2.11.12" } }, + "examples/basic-server-solid/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-solid/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-svelte": { "name": "@modelcontextprotocol/server-basic-svelte", "version": "1.7.4", @@ -216,6 +284,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-server-svelte/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-svelte/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-vanillajs": { "name": "@modelcontextprotocol/server-basic-vanillajs", "version": "1.7.4", @@ -244,6 +329,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-server-vanillajs/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-vanillajs/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/basic-server-vue": { "name": "@modelcontextprotocol/server-basic-vue", "version": "1.7.4", @@ -274,6 +376,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/basic-server-vue/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/basic-server-vue/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/budget-allocator-server": { "name": "@modelcontextprotocol/server-budget-allocator", "version": "1.7.4", @@ -302,6 +421,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/budget-allocator-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/budget-allocator-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/cohort-heatmap-server": { "name": "@modelcontextprotocol/server-cohort-heatmap", "version": "1.7.4", @@ -334,6 +470,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/cohort-heatmap-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/cohort-heatmap-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/customer-segmentation-server": { "name": "@modelcontextprotocol/server-customer-segmentation", "version": "1.7.4", @@ -362,6 +515,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/customer-segmentation-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/customer-segmentation-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/debug-server": { "name": "@modelcontextprotocol/server-debug", "version": "1.7.4", @@ -389,6 +559,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/debug-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/debug-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/integration-server": { "version": "1.7.4", "dependencies": { @@ -419,6 +606,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/integration-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/integration-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/lazy-auth-server": { "name": "@modelcontextprotocol/server-lazy-auth", "version": "1.7.4", @@ -446,6 +650,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/lazy-auth-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/lazy-auth-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/map-server": { "name": "@modelcontextprotocol/server-map", "version": "1.7.4", @@ -474,6 +695,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/map-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/map-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/pdf-server": { "name": "@modelcontextprotocol/server-pdf", "version": "1.7.4", @@ -504,6 +742,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/pdf-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/pdf-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/qr-server": { "name": "@modelcontextprotocol/server-qr", "version": "1.7.4", @@ -536,6 +791,16 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/quickstart/node_modules/@types/node": { + "version": "22.19.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", + "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "examples/say-server": { "name": "@modelcontextprotocol/server-say", "version": "1.7.4", @@ -577,6 +842,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/scenario-modeler-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/scenario-modeler-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/shadertoy-server": { "name": "@modelcontextprotocol/server-shadertoy", "version": "1.7.4", @@ -604,6 +886,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/shadertoy-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/shadertoy-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/sheet-music-server": { "name": "@modelcontextprotocol/server-sheet-music", "version": "1.7.4", @@ -632,6 +931,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/sheet-music-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/sheet-music-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/system-monitor-server": { "name": "@modelcontextprotocol/server-system-monitor", "version": "1.7.4", @@ -661,6 +977,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/system-monitor-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/system-monitor-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/threejs-server": { "name": "@modelcontextprotocol/server-threejs", "version": "1.7.4", @@ -696,6 +1029,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/threejs-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/threejs-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/transcript-server": { "name": "@modelcontextprotocol/server-transcript", "version": "1.7.4", @@ -724,6 +1074,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/transcript-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/transcript-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/video-resource-server": { "name": "@modelcontextprotocol/server-video-resource", "version": "1.7.4", @@ -752,6 +1119,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/video-resource-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/video-resource-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "examples/wiki-explorer-server": { "name": "@modelcontextprotocol/server-wiki-explorer", "version": "1.7.4", @@ -782,6 +1166,23 @@ "vite-plugin-singlefile": "^2.3.0" } }, + "examples/wiki-explorer-server/node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "examples/wiki-explorer-server/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", From 917a689f8ee87958afd3a6dfa409b88d4e61b311 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Wed, 15 Jul 2026 08:30:45 +0000 Subject: [PATCH 8/9] Port App and AppBridge to the public v2 Protocol base class Replaces the Client/Server subclassing from the v2 migration with direct Protocol extension, now that the SDK exports Protocol from the client package root. This restores the v1 wire contract on the iframe channel: ui/initialize is the first and only handshake, so deployed hosts keep working unchanged. Deleted with the double handshake: the inner MCP initialize round-trip and its pinned protocol version, the contentless inner capabilities and their retro-registration plumbing, the oninitialized defineProperty shadow, and the getCapabilities/getHostCapabilities split (v1 getCapabilities restored). Tests pin the wire order and assert no MCP initialize is sent in either direction. Note: package.json overrides point the SDK packages at locally built tarballs carrying the Protocol export; replace with published versions before any upstream PR. --- AGENTS.md | 8 +-- docs/overview.md | 2 +- package-lock.json | 16 ++--- package.json | 11 +-- src/app-bridge.test.ts | 10 +-- src/app-bridge.ts | 150 +++++++++++++++++------------------------ src/app.test.ts | 22 ++---- src/app.ts | 52 +++++--------- 8 files changed, 105 insertions(+), 166 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f850aa3e5..4b0faa066 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \ ### Key Source Files -- `src/app.ts` - `App` subclasses the base MCP SDK `Client`, handles View initialization, tool calls, and messaging -- `src/app-bridge.ts` - `AppBridge` subclasses the base MCP SDK `Server` for the inner iframe channel and proxies through a separate outer `Client` +- `src/app.ts` - `App` subclasses the base MCP SDK `Protocol`, handles View initialization, tool calls, and messaging +- `src/app-bridge.ts` - `AppBridge` subclasses the base MCP SDK `Protocol` for the iframe channel and proxies through a separate outer `Client` - `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata - `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build) - `src/message-transport.ts` - `PostMessageTransport` for iframe communication @@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \ ### Protocol Flow ``` -View (App/Client) <--PostMessageTransport--> Host (AppBridge/Server) <--separate MCP Client--> MCP Server +View (App/Protocol) <--PostMessageTransport--> Host (AppBridge/Protocol) <--separate MCP Client--> MCP Server ``` 1. Host creates iframe with view HTML 2. View creates `App` instance and calls `connect()` with `PostMessageTransport` -3. View and Host complete the temporary inner base MCP handshake, then the View sends `ui/initialize` and receives authoritative Apps capabilities and context +3. View sends `ui/initialize` (the only handshake on the iframe channel) and receives authoritative Apps capabilities and context 4. Host sends `sendToolInput()` with tool arguments after initialization 5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()` 6. Host sends `sendToolResult()` when tool execution completes diff --git a/docs/overview.md b/docs/overview.md index 317ab4300..63156ec72 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -97,7 +97,7 @@ sequenceDiagram ``` 1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server. -2. **Initialization** — When a UI tool is called, the Host renders the iframe. The SDK first completes its temporary inner base MCP handshake, then the View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). `ui/initialize` remains authoritative for Apps capabilities and identity, and `ui/notifications/initialized` remains the public View-ready signal. +2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` (the only handshake on the iframe channel) and receives host context (theme, capabilities, container dimensions). `ui/initialize` is authoritative for Apps capabilities and identity, and `ui/notifications/initialized` is the public View-ready signal. 3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context. 4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context. 5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources. diff --git a/package-lock.json b/package-lock.json index 9e0a71b5f..04b0d2ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,9 @@ }, "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "2.0.0-beta.4", - "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/server": "2.0.0-beta.4", + "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", + "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", + "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -2639,8 +2639,8 @@ }, "node_modules/@modelcontextprotocol/client": { "version": "2.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0-beta.4.tgz", - "integrity": "sha512-VNHA/UXDk7mCpVl+jOg5B4WMRRD2OEl+it360Lhi6HiCbrIB2f6pZ0cqSDKXQVUtZvqnhdQHzZAM9h8EKWhq/A==", + "resolved": "file:../tarballs/mcp-client.tgz", + "integrity": "sha512-aCTBtOjOSOj+SOLsoUfDjhD3Ngs4pMXzAczBmKRTesiz0N/3wUEXEmKgxhfbhF6Wtgk4tatWBUG9LDaj919K2Q==", "license": "MIT", "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.4", @@ -2657,7 +2657,7 @@ }, "node_modules/@modelcontextprotocol/core": { "version": "2.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0-beta.4.tgz", + "resolved": "file:../tarballs/mcp-core.tgz", "integrity": "sha512-nsMXd4wQBKzmph6r+WOhum+mXjDYljTAqwY/XUg3hLtvNOQ8+JVqBSJOVCMJvx9lXhTpTOPrGZ3BuNiaNPjSvg==", "license": "MIT", "dependencies": { @@ -2784,8 +2784,8 @@ }, "node_modules/@modelcontextprotocol/server": { "version": "2.0.0-beta.4", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0-beta.4.tgz", - "integrity": "sha512-pjMZcNEt1dOq0aJCcY3b7w1Ayh+qmQN2xlfTELcGV9yiAjEGIczBPBLWWW0k0zzo5T8kiv15jtKPsWeHGxLvLg==", + "resolved": "file:../tarballs/mcp-server.tgz", + "integrity": "sha512-/UJubByzGV+mlU8fN8yyuMI9zgoq4Ic5b1V/6yhzxrKjmXpTbNkY5rD2jRsWBk+At0AVcXLkcDzmLF91oOJtKw==", "license": "MIT", "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.4", diff --git a/package.json b/package.json index ecbf386a8..59e180293 100644 --- a/package.json +++ b/package.json @@ -77,9 +77,9 @@ "author": "Olivier Chafik", "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "2.0.0-beta.4", - "@modelcontextprotocol/core": "2.0.0-beta.4", - "@modelcontextprotocol/server": "2.0.0-beta.4", + "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", + "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", + "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -129,7 +129,10 @@ "seroval": "1.4.1", "seroval-plugins": "1.4.2", "solid-js": "1.9.10", - "@types/node": "20.19.27" + "@types/node": "20.19.27", + "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", + "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", + "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz" }, "dependencies": { "@standard-schema/spec": "^1.1.0" diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 81e072fd6..a80a9613a 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -82,7 +82,7 @@ describe("App <-> AppBridge integration", () => { expect(initializedFired).toBe(true); }); - it("keeps standard initialization separate from the Apps-ready gate", async () => { + it("sends only the Apps handshake on the wire and gates the Apps-ready callback", async () => { const methods: string[] = []; let releaseAppsInitialized!: () => void; let reachedAppsInitialized!: () => void; @@ -115,14 +115,9 @@ describe("App <-> AppBridge integration", () => { await reachedGate; expect(methods).toEqual([ - "initialize", - "notifications/initialized", "ui/initialize", "ui/notifications/initialized", ]); - expect(app.getNegotiatedProtocolVersion()).toBe("2025-11-25"); - expect(bridge.getNegotiatedProtocolVersion()).toBe("2025-11-25"); - expect(bridge.getClientVersion()).toEqual(testAppInfo); expect(bridge.getAppVersion()).toEqual(testAppInfo); expect(singularCalls).toBe(0); expect(listenerCalls).toBe(0); @@ -143,8 +138,7 @@ describe("App <-> AppBridge integration", () => { const hostCaps = app.getHostCapabilities(); expect(hostCaps).toEqual(testHostCapabilities); - expect(bridge.getHostCapabilities()).toEqual(testHostCapabilities); - expect(bridge.getCapabilities()).toEqual({}); + expect(bridge.getCapabilities()).toEqual(testHostCapabilities); }); it("Bridge receives app info and capabilities after initialization", async () => { diff --git a/src/app-bridge.ts b/src/app-bridge.ts index d353cf71e..467c33dce 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -1,6 +1,8 @@ -import { type Client } from "@modelcontextprotocol/client"; import { - Server, + Protocol, + type BaseContext, + type Client, + type ProtocolOptions, type CallToolRequest, type CallToolResult, type CreateMessageRequest, @@ -24,13 +26,10 @@ import { type RequestOptions, type ResourceListChangedNotification, type Result, - type ServerCapabilities, - type ServerContext, - type ServerOptions, type Tool, type ToolListChangedNotification, type Transport, -} from "@modelcontextprotocol/server"; +} from "@modelcontextprotocol/client"; import { EmptyResultSchema, LoggingMessageNotificationSchema, @@ -43,8 +42,6 @@ type MethodSchema = ZodObject<{ params: ZodType; }>; -const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; - import { type McpUiSandboxResourceReadyNotification, type McpUiSizeChangedNotification, @@ -96,20 +93,6 @@ export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app"; import { RESOURCE_URI_META_KEY } from "./app"; export { PostMessageTransport } from "./message-transport"; -function proxiedInnerCapabilities( - capabilities: ServerCapabilities, -): ServerCapabilities { - return { - ...(capabilities.tools && { tools: capabilities.tools }), - ...(capabilities.resources && { - resources: { - listChanged: capabilities.resources.listChanged, - }, - }), - ...(capabilities.prompts && { prompts: capabilities.prompts }), - }; -} - /** * Extract UI resource URI from tool metadata. * @@ -215,13 +198,10 @@ export function buildAllowAttribute( * * @property hostContext - Optional initial host context to provide to the view * - * @see `ServerOptions` from @modelcontextprotocol/server for available options + * @see `ProtocolOptions` from @modelcontextprotocol/client for available options * @see {@link McpUiHostContext `McpUiHostContext`} for the hostContext structure */ -export type HostOptions = Omit< - ServerOptions, - "capabilities" | "supportedProtocolVersions" -> & { +export type HostOptions = Omit & { hostContext?: McpUiHostContext; }; @@ -241,7 +221,7 @@ export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION]; * * @internal */ -type RequestHandlerExtra = ServerContext; +type RequestHandlerExtra = BaseContext; /** * Maps DOM-style event names to their notification `params` types. @@ -315,7 +295,7 @@ export type AppBridgeEventMap = { * await bridge.connect(transport); * ``` */ -export class AppBridge extends Server { +export class AppBridge extends Protocol { private _appCapabilities?: McpUiAppCapabilities; private _hostContext: McpUiHostContext = {}; private _appInfo?: Implementation; @@ -328,26 +308,17 @@ export class AppBridge extends Server { * handler registration. AppBridge uses it to preserve the existing warning * when a View calls host methods before `ui/notifications/initialized`, * without adding overload-forwarding adapters around `setRequestHandler`. - * Chaining through `super._wrapHandler()` retains the base `Server`'s own - * role-specific behavior, including tool-result validation. The two - * initialization methods and `ping` are exempt because they are valid before - * the Apps-ready gate. This wraps registered callbacks, not JSON-RPC dispatch. + * `ui/initialize` and `ping` are exempt because they are valid before the + * Apps-ready gate. This wraps registered callbacks, not JSON-RPC dispatch. * * @see {@link https://github.com/anthropics/claude-ai-mcp/issues/149 claude-ai-mcp#149} */ protected override _wrapHandler( method: string, - handler: ( - request: JSONRPCRequest, - context: ServerContext, - ) => Promise, - ): (request: JSONRPCRequest, context: ServerContext) => Promise { + handler: (request: JSONRPCRequest, context: BaseContext) => Promise, + ): (request: JSONRPCRequest, context: BaseContext) => Promise { const wrapped = super._wrapHandler(method, handler); - if ( - method === "initialize" || - method === "ui/initialize" || - method === "ping" - ) { + if (method === "ui/initialize" || method === "ping") { return wrapped; } return async (request, context) => { @@ -439,14 +410,6 @@ export class AppBridge extends Server { } } - private ensureInnerCapabilities(capabilities: ServerCapabilities): void { - const current = super.getCapabilities(); - const missing = Object.keys(capabilities).some( - (key) => current[key as keyof ServerCapabilities] === undefined, - ); - if (missing) this.registerCapabilities(capabilities); - } - /** * Create a new AppBridge instance. * @@ -486,30 +449,7 @@ export class AppBridge extends Server { private _hostCapabilities: McpUiHostCapabilities, options?: HostOptions, ) { - // TEMPORARY INNER-CHANNEL NEGOTIATION: - // App and AppBridge use the public v2 Client/Server roles, so the iframe - // channel first completes the SDK's standard legacy MCP initialization. - // ui/initialize remains authoritative for Apps capabilities, identity, and - // host context, and ui/notifications/initialized remains the View-ready gate. - // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only - // after the MCP Apps negotiation specification is resolved: - // https://github.com/modelcontextprotocol/ext-apps/issues/708 - super(_hostInfo, { - ...options, - capabilities: {}, - supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], - }); - - // The base Server's standard initialized callback is MCP-ready state. The - // public AppBridge callback remains exclusively the Apps View-ready gate. - super.setNotificationHandler("notifications/initialized", () => {}); - Object.defineProperty(this, "oninitialized", { - configurable: true, - enumerable: true, - get: () => this.getEventHandler("initialized"), - set: (callback: AppBridge["oninitialized"]) => - this.setEventHandler("initialized", callback), - }); + super(options); this._ensureEventSlot("initialized"); @@ -544,6 +484,35 @@ export class AppBridge extends Server { ); } + /** @internal */ + protected buildContext(ctx: BaseContext): BaseContext { + return ctx; + } + + /** + * Verify that the view supports the capability required for the given request method. + * @internal + */ + protected assertCapabilityForMethod(_method: string): void { + // Host-to-view requests are not capability-gated. + } + + /** + * Verify that a request handler is registered and supported for the given method. + * @internal + */ + protected assertRequestHandlerCapability(_method: string): void { + // Host handler registration is not capability-gated. + } + + /** + * Verify that the host supports the capability required for the given notification method. + * @internal + */ + protected assertNotificationCapability(_method: string): void { + // Host notifications are not capability-gated. + } + /** * Get the view's capabilities discovered during initialization. * @@ -713,9 +682,18 @@ export class AppBridge extends Server { * @see {@link sendToolInput `sendToolInput`} for sending tool arguments to the View * @deprecated Use {@link addEventListener `addEventListener("initialized", handler)`} instead — it composes with other listeners and supports cleanup via {@link removeEventListener `removeEventListener`}. */ - override oninitialized?: ( - params?: McpUiInitializedNotification["params"], - ) => void; + get oninitialized(): + | ((params?: McpUiInitializedNotification["params"]) => void) + | undefined { + return this.getEventHandler("initialized"); + } + set oninitialized( + callback: + | ((params?: McpUiInitializedNotification["params"]) => void) + | undefined, + ) { + this.setEventHandler("initialized", callback); + } /** * Register a handler for message requests from the view. @@ -1178,7 +1156,6 @@ export class AppBridge extends Server { ) { this.warnIfRequestHandlerReplaced("oncalltool", this._oncalltool, callback); this._oncalltool = callback; - this.ensureInnerCapabilities({ tools: {} }); this.setRequestHandler("tools/call", async (request, extra) => { if (!this._oncalltool) throw new Error("No oncalltool handler set"); return this._oncalltool(request.params, extra); @@ -1299,7 +1276,6 @@ export class AppBridge extends Server { callback, ); this._onlistresources = callback; - this.ensureInnerCapabilities({ resources: {} }); this.setRequestHandler("resources/list", async (request, extra) => { if (!this._onlistresources) throw new Error("No onlistresources handler set"); @@ -1353,7 +1329,6 @@ export class AppBridge extends Server { callback, ); this._onlistresourcetemplates = callback; - this.ensureInnerCapabilities({ resources: {} }); this.setRequestHandler( "resources/templates/list", async (request, extra) => { @@ -1410,7 +1385,6 @@ export class AppBridge extends Server { callback, ); this._onreadresource = callback; - this.ensureInnerCapabilities({ resources: {} }); this.setRequestHandler("resources/read", async (request, extra) => { if (!this._onreadresource) throw new Error("No onreadresource handler set"); @@ -1492,7 +1466,6 @@ export class AppBridge extends Server { callback, ); this._onlistprompts = callback; - this.ensureInnerCapabilities({ prompts: {} }); this.setRequestHandler("prompts/list", async (request, extra) => { if (!this._onlistprompts) throw new Error("No onlistprompts handler set"); return this._onlistprompts(request.params, extra); @@ -1532,7 +1505,7 @@ export class AppBridge extends Server { * * @see {@link McpUiHostCapabilities `McpUiHostCapabilities`} for the capabilities structure */ - getHostCapabilities(): McpUiHostCapabilities { + getCapabilities(): McpUiHostCapabilities { return this._hostCapabilities; } @@ -1565,7 +1538,7 @@ export class AppBridge extends Server { return { protocolVersion, - hostCapabilities: this.getHostCapabilities(), + hostCapabilities: this.getCapabilities(), hostInfo: this._hostInfo, hostContext: this._hostContext, }; @@ -1931,7 +1904,6 @@ export class AppBridge extends Server { if (!serverCapabilities) { throw new Error("Client server capabilities not available"); } - this.registerCapabilities(proxiedInnerCapabilities(serverCapabilities)); if (serverCapabilities.tools) { this.oncalltool = async (params, extra) => { @@ -1989,9 +1961,9 @@ export class AppBridge extends Server { } } - // The standard MCP handshake only establishes the temporary inner Server - // role. ui/notifications/initialized remains the public View-ready signal. - + // Protocol.connect only wires the transport — no MCP handshake happens on + // the iframe channel. The View drives ui/initialize; + // ui/notifications/initialized remains the public View-ready signal. return super.connect(transport); } } diff --git a/src/app.test.ts b/src/app.test.ts index dcf2d5287..9057710f4 100644 --- a/src/app.test.ts +++ b/src/app.test.ts @@ -14,8 +14,6 @@ import { McpUiInitializedNotificationSchema, } from "./types"; -const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; - type ConnectedPair = { app: App; server: Server; @@ -34,7 +32,6 @@ async function connectPair( { name: "inner-mcp-server", version: "0.0.0" }, { capabilities: { resources: {}, tools: {} }, - supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], }, ); const [appTransport, serverTransport] = InMemoryTransport.createLinkedPair(); @@ -103,8 +100,8 @@ afterEach(async () => { ); }); -describe("App base MCP SDK v2 Client migration", () => { - it("completes the legacy MCP handshake before the authoritative Apps handshake", async () => { +describe("App base MCP SDK v2 Protocol migration", () => { + it("performs only the Apps handshake — no MCP initialize on the wire", async () => { const pair = await connectPair(); connected.push(pair); @@ -123,25 +120,19 @@ describe("App base MCP SDK v2 Client migration", () => { ].includes(method), ); + // The filter above includes the core MCP lifecycle methods, so this + // equality also proves no `initialize`/`notifications/initialized` was sent + // — the wire contract deployed v1 hosts depend on. expect(lifecycleMethods).toEqual([ - "initialize", - "notifications/initialized", "ui/initialize", "ui/notifications/initialized", ]); - expect(pair.app.getNegotiatedProtocolVersion()).toBe( - INNER_MCP_PROTOCOL_VERSION, - ); }); it("uses ui/initialize as the authority for Apps host state", async () => { const pair = await connectPair(); connected.push(pair); - expect(pair.app.getServerVersion()).toEqual({ - name: "inner-mcp-server", - version: "0.0.0", - }); expect(pair.app.getHostVersion()).toEqual({ name: "apps-host", version: "2.0.0", @@ -219,9 +210,6 @@ describe("App base MCP SDK v2 Client migration", () => { const second = await connectPair(first.app); connected.push(second); - expect(second.app.getNegotiatedProtocolVersion()).toBe( - INNER_MCP_PROTOCOL_VERSION, - ); expect(second.app.getHostVersion()).toEqual({ name: "apps-host", version: "2.0.0", diff --git a/src/app.ts b/src/app.ts index 7e752cec3..c63978403 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,7 @@ import { - Client, - type ClientContext, - type ClientOptions, + Protocol, + type BaseContext, + type ProtocolOptions, type RequestOptions, type CallToolRequest, type CallToolResult, @@ -71,8 +71,6 @@ type MethodSchema = ZodObject<{ params: ZodType; }>; -const INNER_MCP_PROTOCOL_VERSION = "2025-11-25"; - function mergeAppCapabilities( base: McpUiAppCapabilities, additional: McpUiAppCapabilities, @@ -174,14 +172,11 @@ export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; /** * Options for configuring {@link App `App`} behavior. * - * Extends `ClientOptions` from the base MCP SDK with `App`-specific configuration. + * Extends `ProtocolOptions` from the base MCP SDK with `App`-specific configuration. * - * @see `ClientOptions` from @modelcontextprotocol/client for inherited options + * @see `ProtocolOptions` from @modelcontextprotocol/client for inherited options */ -export type AppOptions = Omit< - ClientOptions, - "capabilities" | "supportedProtocolVersions" | "versionNegotiation" -> & { +export type AppOptions = Omit & { /** * Automatically report size changes to the host using `ResizeObserver`. * @@ -221,7 +216,7 @@ export type AppOptions = Omit< allowUnsafeEval?: boolean; }; -type RequestHandlerExtra = ClientContext; +type RequestHandlerExtra = BaseContext; /** * Result of an app-registered tool callback. When `Out` is provided, @@ -354,7 +349,7 @@ export type AppEventMap = { * await app.connect(); * ``` */ -export class App extends Client { +export class App extends Protocol { private _hostCapabilities?: McpUiHostCapabilities; private _hostInfo?: Implementation; private _hostContext?: McpUiHostContext; @@ -521,20 +516,7 @@ export class App extends Client { private _appCapabilities: McpUiAppCapabilities = {}, private options: AppOptions = { autoResize: true }, ) { - // TEMPORARY INNER-CHANNEL NEGOTIATION: - // App and AppBridge use the public v2 Client/Server roles, so the iframe - // channel first completes the SDK's standard legacy MCP initialization. - // ui/initialize remains authoritative for Apps capabilities, identity, and - // host context, and ui/notifications/initialized remains the View-ready gate. - // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only - // after the MCP Apps negotiation specification is resolved: - // https://github.com/modelcontextprotocol/ext-apps/issues/708 - super(_appInfo, { - ...options, - capabilities: {}, - versionNegotiation: { mode: "legacy" }, - supportedProtocolVersions: [INNER_MCP_PROTOCOL_VERSION], - }); + super(options); if (!options.allowUnsafeEval) { z.config({ jitless: true }); @@ -550,6 +532,11 @@ export class App extends Client { this.setEventHandler("hostcontextchanged", undefined); } + /** @internal */ + protected buildContext(ctx: BaseContext): BaseContext { + return ctx; + } + private registerAppCapabilities(capabilities: McpUiAppCapabilities): void { if (this.transport) { throw new Error( @@ -1992,15 +1979,10 @@ export class App extends Client { } this._initializedSent = false; - // TEMPORARY INNER-CHANNEL NEGOTIATION: - // App and AppBridge use the public v2 Client/Server roles, so the iframe - // channel first completes the SDK's standard legacy MCP initialization. - // ui/initialize remains authoritative for Apps capabilities, identity, and - // host context, and ui/notifications/initialized remains the View-ready gate. - // Keep the inner MCP era pinned to 2025-11-25. Revisit this sequence only - // after the MCP Apps negotiation specification is resolved: - // https://github.com/modelcontextprotocol/ext-apps/issues/708 try { + // Protocol.connect only wires the transport — no MCP handshake. The + // ui/initialize request below is the sole negotiation on this channel, + // matching the v1 wire behavior deployed hosts expect. await super.connect(transport); const result = await this.request( From 432d42bb02a569078ab31ab6458428df6027809d Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Wed, 15 Jul 2026 08:34:35 +0000 Subject: [PATCH 9/9] Point SDK packages at the pkg.pr.new preview of typescript-sdk#2501 The Protocol-based migration needs the Protocol class exported from the package roots, which lands in modelcontextprotocol/typescript-sdk#2501. Pin the preview builds so the branch is installable; swap to the next published beta once that PR is released. --- package-lock.json | 20 ++++++++++---------- package.json | 12 ++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04b0d2ea1..e82271b98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,9 @@ }, "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", - "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", - "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz", + "@modelcontextprotocol/client": "https://pkg.pr.new/@modelcontextprotocol/client@2501", + "@modelcontextprotocol/core": "https://pkg.pr.new/@modelcontextprotocol/core@2501", + "@modelcontextprotocol/server": "https://pkg.pr.new/@modelcontextprotocol/server@2501", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -2639,11 +2639,11 @@ }, "node_modules/@modelcontextprotocol/client": { "version": "2.0.0-beta.4", - "resolved": "file:../tarballs/mcp-client.tgz", - "integrity": "sha512-aCTBtOjOSOj+SOLsoUfDjhD3Ngs4pMXzAczBmKRTesiz0N/3wUEXEmKgxhfbhF6Wtgk4tatWBUG9LDaj919K2Q==", + "resolved": "https://pkg.pr.new/@modelcontextprotocol/client@2501", + "integrity": "sha512-5FvmSf2qWlJXlI9ydf8230f1rsDHMEw9Jgcq2ZUGmA3DJShaYV6t49zKJBuawetMcLOzWBGs8+qDzhZOZTk6Fw==", "license": "MIT", "dependencies": { - "@modelcontextprotocol/core": "2.0.0-beta.4", + "@modelcontextprotocol/core": "https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/core@f60b401", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", @@ -2657,7 +2657,7 @@ }, "node_modules/@modelcontextprotocol/core": { "version": "2.0.0-beta.4", - "resolved": "file:../tarballs/mcp-core.tgz", + "resolved": "https://pkg.pr.new/@modelcontextprotocol/core@2501", "integrity": "sha512-nsMXd4wQBKzmph6r+WOhum+mXjDYljTAqwY/XUg3hLtvNOQ8+JVqBSJOVCMJvx9lXhTpTOPrGZ3BuNiaNPjSvg==", "license": "MIT", "dependencies": { @@ -2784,11 +2784,11 @@ }, "node_modules/@modelcontextprotocol/server": { "version": "2.0.0-beta.4", - "resolved": "file:../tarballs/mcp-server.tgz", - "integrity": "sha512-/UJubByzGV+mlU8fN8yyuMI9zgoq4Ic5b1V/6yhzxrKjmXpTbNkY5rD2jRsWBk+At0AVcXLkcDzmLF91oOJtKw==", + "resolved": "https://pkg.pr.new/@modelcontextprotocol/server@2501", + "integrity": "sha512-mI+V52bTdSCHDrsrVG3TeOzgy+apDQXz6l1B4KuwKV/8xbRAsV9LZ2Nf67SEoLszuYYH0as4pZQUG4cCMYP5/g==", "license": "MIT", "dependencies": { - "@modelcontextprotocol/core": "2.0.0-beta.4", + "@modelcontextprotocol/core": "https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/core@f60b401", "zod": "^4.2.0" }, "engines": { diff --git a/package.json b/package.json index 59e180293..e95e0bedc 100644 --- a/package.json +++ b/package.json @@ -77,9 +77,9 @@ "author": "Olivier Chafik", "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", - "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", - "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz", + "@modelcontextprotocol/client": "https://pkg.pr.new/@modelcontextprotocol/client@2501", + "@modelcontextprotocol/core": "https://pkg.pr.new/@modelcontextprotocol/core@2501", + "@modelcontextprotocol/server": "https://pkg.pr.new/@modelcontextprotocol/server@2501", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -130,9 +130,9 @@ "seroval-plugins": "1.4.2", "solid-js": "1.9.10", "@types/node": "20.19.27", - "@modelcontextprotocol/core": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-core.tgz", - "@modelcontextprotocol/client": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-client.tgz", - "@modelcontextprotocol/server": "file:/root/src/mcp/projects/v2-2026-ext-apps/tarballs/mcp-server.tgz" + "@modelcontextprotocol/core": "https://pkg.pr.new/@modelcontextprotocol/core@2501", + "@modelcontextprotocol/client": "https://pkg.pr.new/@modelcontextprotocol/client@2501", + "@modelcontextprotocol/server": "https://pkg.pr.new/@modelcontextprotocol/server@2501" }, "dependencies": { "@standard-schema/spec": "^1.1.0"