diff --git a/bun.lock b/bun.lock index 819236e69..db4bbb42f 100644 --- a/bun.lock +++ b/bun.lock @@ -48,6 +48,7 @@ "@tauri-apps/plugin-shell": "^2.3.5", "@tauri-apps/plugin-updater": "^2.10.1", "@unhead/vue": "^2.1.17", + "@valibot/to-json-schema": "^1.8.0", "@vueuse/core": "^14.4.0", "@xmldom/xmldom": "^0.9.11", "ai": "^7.0.66", @@ -91,7 +92,7 @@ "@figma/plugin-typings": "^1.134.0", "@hono/node-server": "^1.19.17", "@iconify-json/lucide": "^1.2.123", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/client": "^2.0.0", "@open-pencil/mcp": "workspace:*", "@playwright/test": "^1.62.1", "@storybook/addon-a11y": "^10.5.8", @@ -144,6 +145,7 @@ "vite": "8.1.4", "vite-plugin-pwa": "^1.3.0", "vue-tsc": "^3.3.10", + "webmcp-types": "^0.1.7", "workbox-window": "^7.4.1", "ws": "^8.21.3", }, @@ -305,12 +307,13 @@ }, "dependencies": { "@hono/node-server": "^1.19.17", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.0.0", "@open-pencil/core": "workspace:*", + "@valibot/to-json-schema": "^1.8.0", "hono": "^4.13.2", "package-manager-detector": "^1.8.0", + "valibot": "^1.4.2", "ws": "^8.21.3", - "zod": "^4.4.3", }, "devDependencies": { "@types/node": "^25.9.5", @@ -1129,14 +1132,16 @@ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], - "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0-beta.5", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.5", "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" } }, "sha512-YuuNm5f2TMoFQRje1UqVP8TJRjijCXMz4ckvoVpx1cUXuBEmykWQ2d8R536pek6UKcXT41T5nWc4qR1JFIbEmg=="], + "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "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" } }, "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw=="], - "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0-beta.5", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-HKbY9XTbsDy1Y6r2I55TGE3JEapM0vg96e1MUmBIF9LGjos5gjhcIrTz1yvBPLg2aFKHjwhUAQfRdrCEnPxNew=="], + "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA=="], "@modelcontextprotocol/ext-apps": ["@modelcontextprotocol/ext-apps@1.7.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "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" }, "optionalPeers": ["react", "react-dom"] }, "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.30.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA=="], + "@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "zod": "^4.2.0" } }, "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw=="], + "@mongodb-js/zstd": ["@mongodb-js/zstd@7.0.0", "", { "dependencies": { "node-addon-api": "^8.5.0", "prebuild-install": "^7.1.3" } }, "sha512-mQ2s0pYYiav+tzCDR05Zptem8Ey2v8s11lri5RKGhTtL4COVCvVCk5vtyRYNT+9L8qSfyOqqefF9UtnW8mC5jA=="], "@multiformats/dns": ["@multiformats/dns@1.0.15", "", { "dependencies": { "@dnsquery/dns-packet": "^6.1.1", "@libp2p/interface": "^3.1.0", "hashlru": "^2.3.0", "p-queue": "^9.0.0", "progress-events": "^1.0.0", "uint8arrays": "^6.1.1" } }, "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA=="], @@ -1821,7 +1826,7 @@ "@unhead/vue": ["@unhead/vue@2.1.17", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.17" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-pnC8x9HLV3qQXdvWfylUEU25uhfCAy3ly9nmpQz84j9py818DRfU8jOsQ5wjdWtxyU1vX/fW2udfm4jtxUK8Bg=="], - "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.1", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A=="], + "@valibot/to-json-schema": ["@valibot/to-json-schema@1.8.0", "", { "peerDependencies": { "valibot": "^1.5.0" } }, "sha512-a0M+uwCuQZEPAo65NYkFSJ14O5c213KoSZmPdoCfuFvUhfULi4T2Z6Tpjv6lTVMW9RoLm74RpRAjNEa43KcN+g=="], "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], @@ -2011,7 +2016,7 @@ "ai": ["ai@7.0.68", "", { "dependencies": { "@ai-sdk/gateway": "4.0.54", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9QuZOT77wzoxxUC0NcueXhCo3HUHA/1pApIJ9VRyE+9/K+3Innkq4kVhrd9aEnwIviJz2Nga063m+UTsPSdOyw=="], - "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], @@ -2761,7 +2766,7 @@ "iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="], - "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "idb": ["idb@8.0.3", "", {}, "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="], @@ -2865,7 +2870,7 @@ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="], "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], @@ -2995,7 +3000,7 @@ "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], @@ -4171,6 +4176,8 @@ "webdriverio": ["webdriverio@9.30.1", "", { "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", "@wdio/config": "9.30.1", "@wdio/logger": "9.29.1", "@wdio/protocols": "9.30.1", "@wdio/repl": "9.16.2", "@wdio/types": "9.30.1", "@wdio/utils": "9.30.1", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", "grapheme-splitter": "^1.0.4", "htmlfy": "^0.8.1", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "query-selector-shadow-dom": "^1.0.1", "resq": "^1.11.0", "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", "webdriver": "9.30.1" }, "peerDependencies": { "puppeteer-core": ">=22.x || <=24.x" }, "optionalPeers": ["puppeteer-core"] }, "sha512-HvoMHVPj1Ky04h0kEELKilWuzAi0Ctpmfw5V7LStQmOzbXkzfxxMutSB3SezJAYCWCnZ6zsEw7TdDWubWGY7qA=="], + "webmcp-types": ["webmcp-types@0.1.7", "", {}, "sha512-70YszESbCx+ozBejmBPTSc0hMyXim+/Kpj8UOZsHd36ANcYqIfkDbMBTTn5y+tQzQYUYN1x2R+5PseKJtVaLqA=="], + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], "websocket-driver": ["websocket-driver@0.7.5", "", { "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA=="], @@ -4301,6 +4308,8 @@ "@ai-sdk/harness/ai": ["ai@7.0.67", "", { "dependencies": { "@ai-sdk/gateway": "4.0.53", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-83Y/OFx7rylrrgzCsaDPlGeL3QC8qrFQ51LBs6MXm2JVxQLLkrvKmPa1/hOzgyCHM8dsB/2rE1a6OGPNRpmTLQ=="], + "@apideck/better-ajv-errors/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "@arethetypeswrong/core/typescript": ["typescript@5.6.1-rc", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ=="], "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1111.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.8", "@aws-sdk/nested-clients": "^3.997.43", "@aws-sdk/types": "^3.974.4", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-JfljgoVtl+s3Qy21n9a7Z48uCQaOXcN74KJ3TEQfPoB293GrXFSt6HSQJF1sTZ8c/5QedEvd3NjJQMO4u9qa5A=="], @@ -4363,6 +4372,8 @@ "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -4381,6 +4392,8 @@ "@libp2p/crypto/uint8arrays": ["uint8arrays@6.1.1", "", { "dependencies": { "multiformats": "^14.0.0" } }, "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA=="], + "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "@multiformats/dns/@libp2p/interface": ["@libp2p/interface@3.2.5", "", { "dependencies": { "@multiformats/dns": "^1.0.6", "@multiformats/multiaddr": "^13.0.3", "main-event": "^1.0.1", "multiformats": "^14.0.0", "progress-events": "^1.1.0", "uint8arraylist": "^3.0.2" } }, "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA=="], "@multiformats/dns/uint8arrays": ["uint8arrays@6.1.1", "", { "dependencies": { "multiformats": "^14.0.0" } }, "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA=="], @@ -4473,6 +4486,8 @@ "@wdio/types/@types/node": ["@types/node@20.19.43", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="], + "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -4483,6 +4498,8 @@ "body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "body-parser/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "cheerio/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "cheerio/parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], @@ -4521,10 +4538,6 @@ "edgedriver/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], - "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - - "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "eslint/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "espree/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], @@ -4561,8 +4574,6 @@ "jscpd/commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], - "jstransformer/is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="], - "jszip/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -4627,6 +4638,8 @@ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "pi-mcp-adapter/@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0-beta.5", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0-beta.5", "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" } }, "sha512-YuuNm5f2TMoFQRje1UqVP8TJRjijCXMz4ckvoVpx1cUXuBEmykWQ2d8R536pek6UKcXT41T5nWc4qR1JFIbEmg=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "pptxgenjs/@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="], @@ -4643,6 +4656,8 @@ "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + "raw-body/iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], @@ -4667,6 +4682,8 @@ "rolldown-plugin-dts/get-tsconfig": ["get-tsconfig@5.0.0-beta.5", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ=="], + "router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -4741,12 +4758,12 @@ "webdriverio/@types/node": ["@types/node@20.19.43", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="], - "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "workbox-background-sync/idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + "workbox-build/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "workbox-build/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "workbox-build/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -4769,6 +4786,8 @@ "@ai-sdk/harness/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.53", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4ZgwJjv/lGA7ywlD/3oYg6XMkIy1VvVKiSRYB83PyLDApwMGXSF+oT7D5lPYJFhNE5Vr3W2H+SwCFuTsbfgn9w=="], + "@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], "@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], @@ -4803,6 +4822,8 @@ "@libp2p/crypto/protons-runtime/uint8-varint": ["uint8-varint@3.0.0", "", { "dependencies": { "uint8arraylist": "^3.0.1", "uint8arrays": "^6.1.0" } }, "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q=="], + "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@multiformats/dns/@libp2p/interface/@multiformats/multiaddr": ["@multiformats/multiaddr@13.0.3", "", { "dependencies": { "@chainsafe/is-ip": "^2.0.1", "multiformats": "^14.0.0", "uint8-varint": "^3.0.0", "uint8arrays": "^6.1.1" } }, "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw=="], "@multiformats/dns/@libp2p/interface/multiformats": ["multiformats@14.0.5", "", {}, "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ=="], @@ -4937,6 +4958,8 @@ "@wdio/types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "blamer/execa/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], "blamer/execa/human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="], @@ -4973,8 +4996,6 @@ "edgedriver/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "expect-webdriverio/@vitest/snapshot/@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], @@ -5001,6 +5022,8 @@ "parse5-parser-stream/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "pi-mcp-adapter/@modelcontextprotocol/client/@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0-beta.5", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-HKbY9XTbsDy1Y6r2I55TGE3JEapM0vg96e1MUmBIF9LGjos5gjhcIrTz1yvBPLg2aFKHjwhUAQfRdrCEnPxNew=="], + "pptxgenjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], @@ -5209,6 +5232,8 @@ "webdriverio/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "workbox-build/glob/jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], "workbox-build/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], diff --git a/package.json b/package.json index 16eaf690c..abac2d10b 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "@tauri-apps/plugin-shell": "^2.3.5", "@tauri-apps/plugin-updater": "^2.10.1", "@unhead/vue": "^2.1.17", + "@valibot/to-json-schema": "^1.8.0", "@vueuse/core": "^14.4.0", "@xmldom/xmldom": "^0.9.11", "ai": "^7.0.66", @@ -162,7 +163,7 @@ "@figma/plugin-typings": "^1.134.0", "@hono/node-server": "^1.19.17", "@iconify-json/lucide": "^1.2.123", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/client": "^2.0.0", "@open-pencil/mcp": "workspace:*", "@playwright/test": "^1.62.1", "@storybook/addon-a11y": "^10.5.8", @@ -215,6 +216,7 @@ "vite": "8.1.4", "vite-plugin-pwa": "^1.3.0", "vue-tsc": "^3.3.10", + "webmcp-types": "^0.1.7", "workbox-window": "^7.4.1", "ws": "^8.21.3" }, diff --git a/packages/core/src/tools/ai-adapter.ts b/packages/core/src/tools/ai-adapter.ts index 3ceb28661..f8b01f6c0 100644 --- a/packages/core/src/tools/ai-adapter.ts +++ b/packages/core/src/tools/ai-adapter.ts @@ -13,7 +13,8 @@ import type { JSONObject } from '@open-pencil/scene-graph/primitives' import type { FigmaAPI } from '#core/figma-api' -import type { ToolDef, ParamDef, ParamType } from './schema' +import type { ToolDef } from './schema' +import { toolInputEntries } from './validation' export interface ToolLogEntry { tool: string @@ -152,14 +153,9 @@ export function toolsToAI( const result: ToolSet = {} for (const def of tools) { - const shape: Record = {} - for (const [key, param] of Object.entries(def.params)) { - shape[key] = paramToValibot(v, param) - } - const toolOpts: Record = { description: def.description, - inputSchema: valibotSchema(v.object(shape as Record)), + inputSchema: valibotSchema(v.object(toolInputEntries(v, def.params))), execute: async (args: Record) => { const startTime = Date.now() const figma = options.getFigma() @@ -303,30 +299,3 @@ export function buildDebugLog(entries: ToolLogEntry[]): ToolDebugLog { return { entries, duplicates, noopMutations, totalResultBytes } } - -function paramToValibot(v: typeof valibot, param: ParamDef): unknown { - const typeMap: Record unknown> = { - string: () => (param.enum ? v.picklist(param.enum as [string, ...string[]]) : v.string()), - number: () => { - const pipes: unknown[] = [v.number()] - if (param.min !== undefined) pipes.push(v.minValue(param.min)) - if (param.max !== undefined) pipes.push(v.maxValue(param.max)) - return pipes.length > 1 ? v.pipe(...(pipes as [never, never, ...never[]])) : v.number() - }, - boolean: () => v.boolean(), - color: () => v.pipe(v.string(), v.description('Color value (hex like #ff0000 or #ff000080)')), - 'string[]': () => v.pipe(v.array(v.string()), v.minLength(1)) - } - - let schema = typeMap[param.type]() - - if (param.description && param.type !== 'color') { - schema = v.pipe(schema as never, v.description(param.description)) - } - - if (!param.required) { - schema = v.optional(schema as never, param.default as never) - } - - return schema -} diff --git a/packages/core/src/tools/index.ts b/packages/core/src/tools/index.ts index 30a7c872c..d3748ca7f 100644 --- a/packages/core/src/tools/index.ts +++ b/packages/core/src/tools/index.ts @@ -17,6 +17,7 @@ export { toolChangesDocument } from './schema' export type { ToolDef, ParamDef, ParamType } from './schema' +export { toolInputEntries } from './validation' export { toolsToAI, buildDebugLog } from './ai-adapter' export type { ToolLogEntry, ToolDebugLog, AIAdapterOptions, StepBudget } from './ai-adapter' export { calcClusterConfidence, wrapEvalCode } from './analyze' diff --git a/packages/core/src/tools/validation.ts b/packages/core/src/tools/validation.ts new file mode 100644 index 000000000..2e0bc6ccf --- /dev/null +++ b/packages/core/src/tools/validation.ts @@ -0,0 +1,45 @@ +import type * as valibot from 'valibot' + +import type { ParamDef } from './schema' + +/** Shared input contract for AI, MCP, and browser tool adapters. */ +export function toolInputEntries( + v: typeof valibot, + params: Record, + options: { coerceNumbers?: boolean } = {} +): valibot.ObjectEntries { + return Object.fromEntries( + Object.entries(params).map(([name, param]) => [name, paramSchema(v, param, options)]) + ) +} + +function paramSchema( + v: typeof valibot, + param: ParamDef, + options: { coerceNumbers?: boolean } +): valibot.GenericSchema { + let schema: valibot.GenericSchema + switch (param.type) { + case 'string': + schema = param.enum ? v.picklist(param.enum) : v.string() + break + case 'number': { + let number: valibot.GenericSchema = v.number() + if (param.min !== undefined) number = v.pipe(number, v.minValue(param.min)) + if (param.max !== undefined) number = v.pipe(number, v.maxValue(param.max)) + schema = options.coerceNumbers ? v.pipe(v.unknown(), v.transform(Number), number) : number + break + } + case 'boolean': + schema = v.boolean() + break + case 'color': + schema = v.string() + break + case 'string[]': + schema = v.pipe(v.array(v.string()), v.minLength(1)) + break + } + schema = v.pipe(schema, v.description(param.description)) + return param.required ? schema : v.optional(schema, param.default) +} diff --git a/packages/mcp/package.json b/packages/mcp/package.json index f63a08eb1..a0f113754 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -64,12 +64,13 @@ }, "dependencies": { "@hono/node-server": "^1.19.17", - "@modelcontextprotocol/sdk": "^1.30.0", + "@modelcontextprotocol/server": "^2.0.0", "@open-pencil/core": "workspace:*", + "@valibot/to-json-schema": "^1.8.0", "hono": "^4.13.2", "package-manager-detector": "^1.8.0", - "ws": "^8.21.3", - "zod": "^4.4.3" + "valibot": "^1.4.2", + "ws": "^8.21.3" }, "devDependencies": { "@types/node": "^25.9.5", diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 0c625e08f..246f32a1d 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -1,7 +1,7 @@ import { randomBytes } from 'node:crypto' import type { Server as HttpServer } from 'node:http' -import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import type { McpServer } from '@modelcontextprotocol/server' import { Hono } from 'hono' import { cors } from 'hono/cors' import { resolveCommand } from 'package-manager-detector/commands' @@ -59,7 +59,6 @@ function mcpInstallCommand(): Promise { export { fail, ok, type MCPContent, type MCPResult } from '#mcp/result' export { registerTools, type RegisterToolsOptions, type RPCSender } from '#mcp/tool/registration' -export { paramToZod } from '#mcp/tool/schema' export interface ServerOptions { /** TCP port for the HTTP + WebSocket server. Ignored when `withTcp` is false. When set to 0 with `withTcp: true`, binds to an ephemeral port. Defaults to 7600. */ diff --git a/packages/mcp/src/server/sessions.ts b/packages/mcp/src/server/sessions.ts index 5c343b36f..afc73182e 100644 --- a/packages/mcp/src/server/sessions.ts +++ b/packages/mcp/src/server/sessions.ts @@ -1,7 +1,6 @@ import { randomUUID } from 'node:crypto' -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' -import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js' +import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server' export type MCPTransport = { handleRequest: (request: Request) => Promise diff --git a/packages/mcp/src/stdio.ts b/packages/mcp/src/stdio.ts index 46ee3831b..389d331c9 100644 --- a/packages/mcp/src/stdio.ts +++ b/packages/mcp/src/stdio.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -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 { resolveMCPRoot } from '#mcp/root' import { MCP_VERSION, registerTools } from '#mcp/server' diff --git a/packages/mcp/src/tool/registration.ts b/packages/mcp/src/tool/registration.ts index 97fb999d1..44e1eae87 100644 --- a/packages/mcp/src/tool/registration.ts +++ b/packages/mcp/src/tool/registration.ts @@ -1,11 +1,12 @@ import { Buffer } from 'node:buffer' import { resolve } from 'node:path' -import type { McpServer, ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js' -import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js' -import { z } from 'zod' +import type { McpServer, ToolCallback, ToolAnnotations } from '@modelcontextprotocol/server' +// eslint-disable-next-line open-pencil/no-mixed-case-acronym-identifiers -- Upstream export spelling. +import { toStandardJsonSchema as toStandardJSONSchema } from '@valibot/to-json-schema' +import * as v from 'valibot' -import { ALL_TOOLS, CODEGEN_PROMPT } from '@open-pencil/core/tools' +import { ALL_TOOLS, CODEGEN_PROMPT, toolInputEntries } from '@open-pencil/core/tools' import type { RPCJSONObject } from '#mcp/json' import { MAX_RESULT_BYTES, fail, ok, resultTooLargeMessage } from '#mcp/result' @@ -13,13 +14,17 @@ import { createToolDescriptors } from '#mcp/tool/manifest' import type { ToolDescriptor, ToolEffect, ToolPolicy } from '#mcp/tool/metadata' import { resolveSafePath, writeToolOutput } from '#mcp/tool/output' import { isToolEnabled } from '#mcp/tool/policy' -import { paramToZod } from '#mcp/tool/schema' +import { toolSchema } from '#mcp/tool/schema' export type RPCSender = (body: Record) => Promise const automationTargetSchema = { - document_id: z.string().describe('Optional OpenPencil document/tab ID to target').optional(), - page_id: z.string().describe('Optional page ID to target within the document').optional() + document_id: v.optional( + v.pipe(v.string(), v.description('Optional OpenPencil document/tab ID to target')) + ), + page_id: v.optional( + v.pipe(v.string(), v.description('Optional page ID to target within the document')) + ) } function splitAutomationTarget(args: Record): { @@ -54,10 +59,10 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption const { policy, sendRPC } = options const resolvedRoot = options.mcpRoot ? resolve(options.mcpRoot) : null const descriptors = descriptorByName(createToolDescriptors(resolvedRoot !== null)) - const register = ( + const register = ( name: string, toolOptions: { description: string; inputSchema: InputArgs }, - handler: ToolCallback + handler: ToolCallback>> ) => { const descriptor = descriptors.get(name) if (!descriptor) throw new Error(`Missing MCP tool descriptor for "${name}"`) @@ -66,6 +71,7 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption name, { ...toolOptions, + inputSchema: toStandardJSONSchema(toolOptions.inputSchema), annotations: toolAnnotations(descriptor.effect), _meta: { 'openpencil/capabilities': descriptor.capabilities } }, @@ -74,15 +80,12 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption } for (const def of ALL_TOOLS) { - const shape: Record = {} - for (const [key, param] of Object.entries(def.params)) { - shape[key] = paramToZod(param) - } + const shape = toolInputEntries(v, def.params) register( def.name, { description: def.description, - inputSchema: z.object({ ...shape, ...automationTargetSchema }) + inputSchema: toolSchema(def.params, shape, automationTargetSchema) }, async (args: Record) => { try { @@ -136,7 +139,7 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption { description: 'List open OpenPencil documents/tabs with their IDs, file paths, current pages, and pages.', - inputSchema: z.object({}) + inputSchema: v.object({}) }, async () => { try { @@ -157,15 +160,17 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption ? 'Save the current document to disk. If path is provided, it must be inside the configured MCP root.' : 'Save the current document to disk. Uses the existing file path if available, otherwise prompts for a location.', inputSchema: resolvedRoot - ? z.object({ - path: z - .string() - .min(1) - .describe('Path for the .fig file, absolute or relative to the MCP root') - .optional(), + ? v.object({ + path: v.optional( + v.pipe( + v.string(), + v.minLength(1), + v.description('Path for the .fig file, absolute or relative to the MCP root') + ) + ), ...automationTargetSchema }) - : z.object({ ...automationTargetSchema }) + : v.object({ ...automationTargetSchema }) }, async (args: { path?: string; document_id?: string; page_id?: string }) => { try { @@ -195,11 +200,12 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption 'open_file', { description: 'Open a .fig or .pen file from inside the configured MCP root.', - inputSchema: z.object({ - path: z - .string() - .min(1) - .describe('Path to the design file, absolute or relative to the MCP root'), + inputSchema: v.object({ + path: v.pipe( + v.string(), + v.minLength(1), + v.description('Path to the design file, absolute or relative to the MCP root') + ), ...automationTargetSchema }) }, @@ -227,12 +233,14 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption { description: 'Create a new empty document with an optional save path inside the configured MCP root.', - inputSchema: z.object({ - path: z - .string() - .min(1) - .describe('Path for the new file, absolute or relative to the MCP root') - .optional(), + inputSchema: v.object({ + path: v.optional( + v.pipe( + v.string(), + v.minLength(1), + v.description('Path for the new file, absolute or relative to the MCP root') + ) + ), ...automationTargetSchema }) }, @@ -262,7 +270,7 @@ export function registerTools(mcpServer: McpServer, options: RegisterToolsOption { description: 'Get design-to-code generation guidelines. Call before generating frontend code.', - inputSchema: z.object({}) + inputSchema: v.object({}) }, async () => ok({ prompt: CODEGEN_PROMPT }) ) diff --git a/packages/mcp/src/tool/schema.ts b/packages/mcp/src/tool/schema.ts index b99ea29fc..ede45cc40 100644 --- a/packages/mcp/src/tool/schema.ts +++ b/packages/mcp/src/tool/schema.ts @@ -1,24 +1,23 @@ -import { z } from 'zod' +import * as v from 'valibot' -import type { ParamDef, ParamType } from '@open-pencil/core/tools' +import { toolInputEntries, type ParamDef } from '@open-pencil/core/tools' -export function paramToZod(param: ParamDef): z.ZodType { - const typeMap: Record z.ZodType> = { - string: () => - param.enum - ? z.enum(param.enum as [string, ...string[]]).describe(param.description) - : z.string().describe(param.description), - number: () => { - let schema = z.coerce.number() - if (param.min !== undefined) schema = schema.min(param.min) - if (param.max !== undefined) schema = schema.max(param.max) - return schema.describe(param.description) - }, - boolean: () => z.boolean().describe(param.description), - color: () => z.string().describe(param.description), - 'string[]': () => z.array(z.string()).min(1).describe(param.description) +/** Advertise canonical numeric inputs while preserving MCP's numeric coercion. */ +export function toolSchema( + params: Record, + entries: v.ObjectEntries, + targetEntries: v.ObjectEntries +): v.ObjectSchema { + const schema = v.object({ ...entries, ...targetEntries }) + const parser = v.object({ + ...toolInputEntries(v, params, { coerceNumbers: true }), + ...targetEntries + }) + return { + ...schema, + '~standard': { + ...schema['~standard'], + validate: parser['~standard'].validate + } } - - const schema = typeMap[param.type]() - return param.required ? schema : schema.optional() } diff --git a/packages/mcp/tsdown.config.ts b/packages/mcp/tsdown.config.ts index 2fe961eb9..e15965f1f 100644 --- a/packages/mcp/tsdown.config.ts +++ b/packages/mcp/tsdown.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ deps: { neverBundle: [ '@hono/node-server', - '@modelcontextprotocol/sdk', + '@modelcontextprotocol/server', '@open-pencil/core', /^@open-pencil\/core\//, 'hono', diff --git a/tests/engine/mcp/server/index.test.ts b/tests/engine/mcp/server/index.test.ts index 9845bdd21..4f7e4d3a8 100644 --- a/tests/engine/mcp/server/index.test.ts +++ b/tests/engine/mcp/server/index.test.ts @@ -5,8 +5,7 @@ import { mkdir, stat } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' +import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client' import { SceneGraph } from '@open-pencil/scene-graph' diff --git a/tests/engine/mcp/server/security.test.ts b/tests/engine/mcp/server/security.test.ts index 1bb4004d3..e80c9597b 100644 --- a/tests/engine/mcp/server/security.test.ts +++ b/tests/engine/mcp/server/security.test.ts @@ -3,12 +3,14 @@ import { mkdir } from 'node:fs/promises' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' -import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' +import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client' +import * as v from 'valibot' +import { toolInputEntries } from '@open-pencil/core/tools' import { SceneGraph } from '@open-pencil/scene-graph' -import { startServer, paramToZod } from '#mcp/server' +import { startServer } from '#mcp/server' +import { toolSchema } from '#mcp/tool/schema' import type { DiscoveryInfo } from '#mcp/transport/discovery' import { @@ -192,33 +194,29 @@ describe('MCP server /rpc auth skip', () => { }) // --------------------------------------------------------------------------- -// paramToZod coercion +// MCP numeric input coercion // --------------------------------------------------------------------------- -describe('paramToZod coercion', () => { - test('number param accepts numeric strings', () => { - const schema = paramToZod({ type: 'number', description: 'x', required: true }) - expect(schema.parse('42')).toBe(42) - expect(schema.parse(42)).toBe(42) - expect(schema.parse('3.14')).toBeCloseTo(3.14) +describe('MCP numeric input coercion', () => { + const params = { + x: { type: 'number' as const, description: 'x', required: true, min: 0, max: 100 } + } + const schema = toolSchema(params, toolInputEntries(v, params), {}) + + test('accepts numeric strings through Standard Schema validation', async () => { + expect(await schema['~standard'].validate({ x: '42' })).toMatchObject({ value: { x: 42 } }) + expect(await schema['~standard'].validate({ x: 42 })).toMatchObject({ value: { x: 42 } }) + expect(await schema['~standard'].validate({ x: '3.14' })).toMatchObject({ value: { x: 3.14 } }) }) - test('number param rejects non-numeric strings', () => { - const schema = paramToZod({ type: 'number', description: 'x', required: true }) - expect(() => schema.parse('abc')).toThrow() + test('rejects non-numeric strings', async () => { + expect((await schema['~standard'].validate({ x: 'abc' })).issues).toBeDefined() }) - test('number param respects min/max after coercion', () => { - const schema = paramToZod({ - type: 'number', - description: 'x', - required: true, - min: 0, - max: 100 - }) - expect(schema.parse('50')).toBe(50) - expect(() => schema.parse('200')).toThrow() - expect(() => schema.parse('-1')).toThrow() + test('respects min/max after coercion', async () => { + expect(await schema['~standard'].validate({ x: '50' })).toMatchObject({ value: { x: 50 } }) + expect((await schema['~standard'].validate({ x: '200' })).issues).toBeDefined() + expect((await schema['~standard'].validate({ x: '-1' })).issues).toBeDefined() }) }) diff --git a/tests/engine/mcp/stdio/index.test.ts b/tests/engine/mcp/stdio/index.test.ts index ace3e54e7..c7c6d1582 100644 --- a/tests/engine/mcp/stdio/index.test.ts +++ b/tests/engine/mcp/stdio/index.test.ts @@ -3,8 +3,8 @@ import { mkdir, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' -import type { Client } from '@modelcontextprotocol/sdk/client/index.js' -import type { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' +import type { Client } from '@modelcontextprotocol/client' +import type { StdioClientTransport } from '@modelcontextprotocol/client/stdio' import { SceneGraph } from '@open-pencil/scene-graph' @@ -24,8 +24,8 @@ const SOCKET_DIR = join(tmpdir(), `openpencil-test-stdio-${process.pid}`) const SOCKET_PATH = isUnix ? join(SOCKET_DIR, 'mcp.sock') : null async function createStdioClient(socketPath: string, authToken: string | null) { - const { Client } = await import('@modelcontextprotocol/sdk/client/index.js') - const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js') + const { Client } = await import('@modelcontextprotocol/client') + const { StdioClientTransport } = await import('@modelcontextprotocol/client/stdio') const env: Record = {} for (const [key, value] of Object.entries(process.env)) { if (!key.startsWith('OPENPENCIL_MCP_') && value !== undefined) {