feat(server): add getUiCapability helper for capability negotiation (#313)
* feat(server): add hasUiSupport/getUiCapability for experimental+extensions Support double-tagging for MCP Apps capability negotiation: - Check both experimental and extensions fields in client capabilities - Add hasUiSupport() to easily check if client supports MCP Apps - Add getUiCapability() to retrieve the capability settings - Update spec to document both capability locations This enables forward compatibility as MCP transitions from experimental to the extensions field (SEP-1724). Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 0 Claude-Permission-Prompts: 0 Claude-Escapes: 0 * fix: update @types/node version in package-lock.json Replace invalid 22.19.5/22.19.6/22.19.7 versions with valid 22.19.3 * docs(spec): clarify hosts SHOULD double-tag, servers SHOULD check both - Hosts SHOULD advertise capabilities in both experimental and extensions - Servers SHOULD check both locations, preferring extensions when present - Updated example to show double-tagging pattern * chore: remove double-tagging recommendation, keep hasUiSupport helper Reverts the spec changes that recommended clients double-tag capabilities in both experimental and extensions fields. The helper functions remain as they're useful for checking capability in either location. * refactor(server): simplify to only getUiCapability, remove hasUiSupport - Remove hasUiSupport function (use getUiCapability directly) - Remove double-tagging logic (only check extensions field) - Add ClientCapabilitiesWithExtensions type using SDK's ClientCapabilities - Update spec to use getUiCapability - Simplify tests * fix: update @see link to getUiCapability * refactor(server): inline ClientCapabilitiesWithExtensions type in getUiCapability * refactor: move McpUiClientCapabilities to spec.types.ts
This commit is contained in:
parent
1003c17c2c
commit
cfcbc9605f
9 changed files with 238 additions and 73 deletions
144
package-lock.json
generated
144
package-lock.json
generated
|
|
@ -476,7 +476,7 @@
|
|||
"version": "0.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "^0.4.0",
|
||||
"@modelcontextprotocol/ext-apps": "^0.4.1",
|
||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
|
|
@ -625,12 +625,18 @@
|
|||
},
|
||||
"examples/qr-server": {
|
||||
"name": "@modelcontextprotocol/server-qr",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "^0.4.1"
|
||||
}
|
||||
},
|
||||
"examples/say-server": {
|
||||
"name": "@modelcontextprotocol/server-say",
|
||||
"version": "0.4.1",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/ext-apps": "^0.4.1"
|
||||
}
|
||||
},
|
||||
"examples/scenario-modeler-server": {
|
||||
"name": "@modelcontextprotocol/server-scenario-modeler",
|
||||
|
|
@ -993,7 +999,6 @@
|
|||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/generator": "^7.28.6",
|
||||
|
|
@ -3106,6 +3111,19 @@
|
|||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz",
|
||||
|
|
@ -3514,7 +3532,6 @@
|
|||
"integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
|
||||
"debug": "^4.4.1",
|
||||
|
|
@ -3721,7 +3738,6 @@
|
|||
"integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
|
|
@ -3746,7 +3762,6 @@
|
|||
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
|
|
@ -4149,7 +4164,6 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -4259,6 +4273,19 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
|
@ -4475,7 +4502,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
|
|
@ -5191,7 +5217,6 @@
|
|||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
@ -5676,7 +5701,6 @@
|
|||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
|
|
@ -6090,6 +6114,16 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.5",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.5.tgz",
|
||||
"integrity": "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
|
||||
|
|
@ -6790,6 +6824,19 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
|
|
@ -7137,13 +7184,13 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
|
|
@ -7238,7 +7285,6 @@
|
|||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
|
||||
"integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
|
|
@ -7334,7 +7380,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -7374,6 +7419,19 @@
|
|||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
|
@ -7433,7 +7491,6 @@
|
|||
"integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
|
|
@ -7555,7 +7612,6 @@
|
|||
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz",
|
||||
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
|
|
@ -7846,7 +7902,6 @@
|
|||
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz",
|
||||
"integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.0",
|
||||
"seroval": "~1.3.0",
|
||||
|
|
@ -8025,7 +8080,6 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.0.tgz",
|
||||
"integrity": "sha512-+NUe82VoFP1RQViZI/esojx70eazGF4u0O/9ucqZ4rPcOZD+n5EVp17uYsqwdzjUjZyTpGKunHbDziW6AIAVkQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
|
|
@ -8329,20 +8383,6 @@
|
|||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
|
|
@ -9096,9 +9136,8 @@
|
|||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -9192,7 +9231,6 @@
|
|||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
|
@ -9345,20 +9383,6 @@
|
|||
"vite": "5.x || 6.x || 7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vitefu": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
|
||||
|
|
@ -9452,25 +9476,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
|
||||
"integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-sfc": "3.5.27",
|
||||
|
|
@ -9643,7 +9653,6 @@
|
|||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
|
@ -9694,7 +9703,6 @@
|
|||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1457,13 +1457,13 @@ Future versions may add additional settings:
|
|||
|
||||
#### Server Behavior
|
||||
|
||||
Servers SHOULD check client (host would-be) capabilities before registering UI-enabled tools:
|
||||
Servers SHOULD check client capabilities before registering UI-enabled tools. The SDK provides the `getUiCapability` helper for this:
|
||||
|
||||
```typescript
|
||||
const hasUISupport =
|
||||
clientCapabilities?.extensions?.["io.modelcontextprotocol/ui"]?.mimeTypes?.includes("text/html;profile=mcp-app");
|
||||
import { getUiCapability, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
||||
|
||||
if (hasUISupport) {
|
||||
const uiCap = getUiCapability(clientCapabilities);
|
||||
if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
|
||||
// Register tools with UI templates
|
||||
server.registerTool("get_weather", {
|
||||
description: "Get weather with interactive dashboard",
|
||||
|
|
|
|||
14
src/generated/schema.json
generated
14
src/generated/schema.json
generated
|
|
@ -28,6 +28,20 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"McpUiClientCapabilities": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mimeTypes": {
|
||||
"description": "Array of supported MIME types for UI resources.\nMust include `\"text/html;profile=mcp-app\"` for MCP Apps support.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"McpUiDisplayMode": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"anyOf": [
|
||||
|
|
|
|||
10
src/generated/schema.test.ts
generated
10
src/generated/schema.test.ts
generated
|
|
@ -119,6 +119,10 @@ export type McpUiToolMetaSchemaInferredType = z.infer<
|
|||
typeof generated.McpUiToolMetaSchema
|
||||
>;
|
||||
|
||||
export type McpUiClientCapabilitiesSchemaInferredType = z.infer<
|
||||
typeof generated.McpUiClientCapabilitiesSchema
|
||||
>;
|
||||
|
||||
export type McpUiMessageRequestSchemaInferredType = z.infer<
|
||||
typeof generated.McpUiMessageRequestSchema
|
||||
>;
|
||||
|
|
@ -277,6 +281,12 @@ expectType<McpUiToolVisibilitySchemaInferredType>(
|
|||
);
|
||||
expectType<spec.McpUiToolMeta>({} as McpUiToolMetaSchemaInferredType);
|
||||
expectType<McpUiToolMetaSchemaInferredType>({} as spec.McpUiToolMeta);
|
||||
expectType<spec.McpUiClientCapabilities>(
|
||||
{} as McpUiClientCapabilitiesSchemaInferredType,
|
||||
);
|
||||
expectType<McpUiClientCapabilitiesSchemaInferredType>(
|
||||
{} as spec.McpUiClientCapabilities,
|
||||
);
|
||||
expectType<spec.McpUiMessageRequest>(
|
||||
{} as McpUiMessageRequestSchemaInferredType,
|
||||
);
|
||||
|
|
|
|||
20
src/generated/schema.ts
generated
20
src/generated/schema.ts
generated
|
|
@ -562,6 +562,26 @@ export const McpUiToolMetaSchema = z.object({
|
|||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* @description MCP Apps capability settings advertised by clients to servers.
|
||||
*
|
||||
* Clients advertise these capabilities via the `extensions` field in their
|
||||
* capabilities during MCP initialization. Servers can check for MCP Apps
|
||||
* support using {@link server-helpers!getUiCapability}.
|
||||
*/
|
||||
export const McpUiClientCapabilitiesSchema = z.object({
|
||||
/**
|
||||
* @description Array of supported MIME types for UI resources.
|
||||
* Must include `"text/html;profile=mcp-app"` for MCP Apps support.
|
||||
*/
|
||||
mimeTypes: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
'Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.',
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Request to send a message to the host's chat interface.
|
||||
* @see {@link app!App.sendMessage `App.sendMessage`} for the method that sends this request
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import {
|
|||
registerAppResource,
|
||||
RESOURCE_URI_META_KEY,
|
||||
RESOURCE_MIME_TYPE,
|
||||
getUiCapability,
|
||||
EXTENSION_ID,
|
||||
} from "./index";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
|
|
@ -318,3 +320,39 @@ describe("registerAppResource", () => {
|
|||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUiCapability", () => {
|
||||
const MIME_TYPE = "text/html;profile=mcp-app";
|
||||
|
||||
it("should return undefined for null/undefined capabilities", () => {
|
||||
expect(getUiCapability(null)).toBeUndefined();
|
||||
expect(getUiCapability(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return undefined for empty capabilities", () => {
|
||||
expect(getUiCapability({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return capability from extensions field", () => {
|
||||
const caps = {
|
||||
extensions: {
|
||||
[EXTENSION_ID]: {
|
||||
mimeTypes: [MIME_TYPE],
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = getUiCapability(caps);
|
||||
expect(result).toEqual({ mimeTypes: [MIME_TYPE] });
|
||||
});
|
||||
|
||||
it("should return undefined when extension ID is missing", () => {
|
||||
const caps = {
|
||||
extensions: {
|
||||
"some-other-extension": {
|
||||
mimeTypes: [MIME_TYPE],
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(getUiCapability(caps)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
RESOURCE_MIME_TYPE,
|
||||
McpUiResourceMeta,
|
||||
McpUiToolMeta,
|
||||
McpUiClientCapabilities,
|
||||
} from "../app.js";
|
||||
import type {
|
||||
BaseToolCallback,
|
||||
|
|
@ -49,7 +50,10 @@ import type {
|
|||
AnySchema,
|
||||
ZodRawShapeCompat,
|
||||
} from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
||||
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
ClientCapabilities,
|
||||
ToolAnnotations,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
// Re-exports for convenience
|
||||
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE };
|
||||
|
|
@ -313,3 +317,58 @@ export function registerAppResource(
|
|||
readCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension identifier for MCP Apps capability negotiation.
|
||||
*
|
||||
* Used as the key in `extensions` to advertise MCP Apps support.
|
||||
*/
|
||||
export const EXTENSION_ID = "io.modelcontextprotocol/ui";
|
||||
|
||||
/**
|
||||
* Get MCP Apps capability settings from client capabilities.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @example Check for MCP Apps support in server initialization
|
||||
* ```typescript
|
||||
* import { getUiCapability, RESOURCE_MIME_TYPE, registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
||||
*
|
||||
* server.oninitialized = ({ clientCapabilities }) => {
|
||||
* const uiCap = getUiCapability(clientCapabilities);
|
||||
* if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
|
||||
* registerAppTool(server, "weather", {
|
||||
* description: "Get weather with interactive dashboard",
|
||||
* _meta: { ui: { resourceUri: "ui://weather/dashboard" } },
|
||||
* }, weatherHandler);
|
||||
* } else {
|
||||
* // Register text-only fallback
|
||||
* server.registerTool("weather", {
|
||||
* description: "Get weather as text",
|
||||
* }, textWeatherHandler);
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export function getUiCapability(
|
||||
clientCapabilities:
|
||||
| (ClientCapabilities & { extensions?: Record<string, unknown> })
|
||||
| null
|
||||
| undefined,
|
||||
): McpUiClientCapabilities | undefined {
|
||||
if (!clientCapabilities) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return clientCapabilities.extensions?.[EXTENSION_ID] as
|
||||
| McpUiClientCapabilities
|
||||
| undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,3 +672,18 @@ export const INITIALIZED_METHOD: McpUiInitializedNotification["method"] =
|
|||
"ui/notifications/initialized";
|
||||
export const REQUEST_DISPLAY_MODE_METHOD: McpUiRequestDisplayModeRequest["method"] =
|
||||
"ui/request-display-mode";
|
||||
|
||||
/**
|
||||
* @description MCP Apps capability settings advertised by clients to servers.
|
||||
*
|
||||
* Clients advertise these capabilities via the `extensions` field in their
|
||||
* capabilities during MCP initialization. Servers can check for MCP Apps
|
||||
* support using {@link server-helpers!getUiCapability}.
|
||||
*/
|
||||
export interface McpUiClientCapabilities {
|
||||
/**
|
||||
* @description Array of supported MIME types for UI resources.
|
||||
* Must include `"text/html;profile=mcp-app"` for MCP Apps support.
|
||||
*/
|
||||
mimeTypes?: string[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export {
|
|||
type McpUiRequestDisplayModeResult,
|
||||
type McpUiToolVisibility,
|
||||
type McpUiToolMeta,
|
||||
type McpUiClientCapabilities,
|
||||
} from "./spec.types.js";
|
||||
|
||||
// Import types needed for protocol type unions (not re-exported, just used internally)
|
||||
|
|
|
|||
Loading…
Reference in a new issue