diff --git a/esbuild.js b/esbuild.js index 7907dd1c390..9ca34fd59d2 100644 --- a/esbuild.js +++ b/esbuild.js @@ -72,6 +72,14 @@ const extensionConfig = { copyWasmFiles, /* add to the end of plugins array */ esbuildProblemMatcherPlugin, + { + name: "alias-plugin", + setup(build) { + build.onResolve({ filter: /^pkce-challenge$/ }, (args) => { + return { path: require.resolve("pkce-challenge/dist/index.browser.js") } + }) + }, + }, ], entryPoints: ["src/extension.ts"], format: "cjs", diff --git a/package-lock.json b/package-lock.json index 1e639f38eb5..625367109fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@google-cloud/vertexai": "^1.9.3", "@google/generative-ai": "^0.18.0", "@mistralai/mistralai": "^1.3.6", - "@modelcontextprotocol/sdk": "^1.0.1", + "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", "@types/pdf-parse": "^1.1.4", "@types/tmp": "^0.2.6", @@ -42,10 +42,12 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", + "pkce-challenge": "^4.1.0", "posthog-node": "^4.7.0", "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", + "reconnecting-eventsource": "^1.6.4", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", "sound-play": "^1.1.0", @@ -4082,13 +4084,41 @@ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz", - "integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", + "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", + "license": "MIT", "dependencies": { "content-type": "^1.0.5", + "cors": "^2.8.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz", + "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" } }, "node_modules/@noble/ciphers": { @@ -6364,6 +6394,40 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -6852,6 +6916,53 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" }, + "node_modules/body-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", + "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.5.2", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -6958,6 +7069,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -6981,10 +7093,10 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.0.tgz", - "integrity": "sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -6993,6 +7105,22 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -7375,10 +7503,23 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7389,11 +7530,42 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -7652,10 +7824,21 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -7813,12 +7996,12 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -7879,6 +8062,12 @@ "node": ">=16" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/eight-colors": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz", @@ -7924,6 +8113,15 @@ "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding-sniffer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", @@ -8087,7 +8285,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8096,16 +8293,15 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -8191,6 +8387,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8431,6 +8633,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -8445,6 +8656,27 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true }, + "node_modules/eventsource": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.5.tgz", + "integrity": "sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -8499,6 +8731,108 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/express/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8718,6 +9052,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8887,6 +9238,24 @@ "node": ">= 12.20" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -8924,7 +9293,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9081,16 +9449,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9108,6 +9481,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9271,7 +9657,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9351,7 +9736,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9383,7 +9767,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -9425,6 +9808,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -9636,6 +10020,15 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -9895,6 +10288,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz", @@ -11827,6 +12226,24 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -11836,6 +12253,18 @@ "node": ">= 0.10.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -11849,6 +12278,15 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -11996,6 +12434,15 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -12303,11 +12750,19 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12352,6 +12807,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -12674,6 +13141,15 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12732,6 +13208,15 @@ "node": "20 || >=22" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", @@ -12815,6 +13300,15 @@ "node": ">= 6" } }, + "node_modules/pkce-challenge": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", + "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -13002,6 +13496,19 @@ "node": ">= 6" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-agent": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", @@ -13095,6 +13602,21 @@ } ] }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13119,10 +13641,20 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/raw-body": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -13257,6 +13789,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reconnecting-eventsource": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/reconnecting-eventsource/-/reconnecting-eventsource-1.6.4.tgz", + "integrity": "sha512-0L3IS3wxcNFApTPPHkcbY8Aya7XZIpYDzhxa8j6QSufVkUN018XJKfh2ZaThLBGP/iN5UTz2yweMhkqr0PKa7A==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", @@ -13479,6 +14020,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/router": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", + "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", + "license": "MIT", + "dependencies": { + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13577,6 +14132,38 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", @@ -13602,6 +14189,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serve-static": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -13642,7 +14244,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -13687,15 +14290,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13930,6 +14587,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14384,6 +15042,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -14507,6 +15166,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -14687,6 +15381,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -14740,6 +15435,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -14776,6 +15480,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index bf397d5351b..1e1b0ab0d8d 100644 --- a/package.json +++ b/package.json @@ -318,7 +318,7 @@ "@google-cloud/vertexai": "^1.9.3", "@google/generative-ai": "^0.18.0", "@mistralai/mistralai": "^1.3.6", - "@modelcontextprotocol/sdk": "^1.0.1", + "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", "@types/pdf-parse": "^1.1.4", "@types/tmp": "^0.2.6", @@ -345,10 +345,12 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", + "pkce-challenge": "^4.1.0", "posthog-node": "^4.7.0", "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", + "reconnecting-eventsource": "^1.6.4", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", "sound-play": "^1.1.0", diff --git a/src/__mocks__/@modelcontextprotocol/sdk/client/sse.js b/src/__mocks__/@modelcontextprotocol/sdk/client/sse.js new file mode 100644 index 00000000000..b52145d25a6 --- /dev/null +++ b/src/__mocks__/@modelcontextprotocol/sdk/client/sse.js @@ -0,0 +1,14 @@ +class SSEClientTransport { + constructor(url, options = {}) { + this.url = url + this.options = options + this.onerror = null + this.connect = jest.fn().mockResolvedValue() + this.close = jest.fn().mockResolvedValue() + this.start = jest.fn().mockResolvedValue() + } +} + +module.exports = { + SSEClientTransport, +} diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 8b5afe98063..140f0dcf55a 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3662,7 +3662,9 @@ export class Cline extends EventEmitter { }) const timeZone = formatter.resolvedOptions().timeZone const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation - const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : ""}${timeZoneOffset}:00` + const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset)) + const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60)) + const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}` details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})` // Add context tokens information diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 74607d6c4f2..5ae5f625fc3 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -148,6 +148,7 @@ jest.mock("vscode", () => { all: [mockTabGroup], onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })), }, + showErrorMessage: jest.fn(), }, workspace: { workspaceFolders: [ diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap index 0202ebcef7d..69b3a0b55b3 100644 --- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap +++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap @@ -2827,7 +2827,10 @@ By waiting for and carefully considering the user's response after each tool use MCP SERVERS -The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. +The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types: + +1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output +2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS # Connected MCP Servers @@ -2841,13 +2844,51 @@ The user may ask you something along the lines of "add a tool" that does some fu When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). -Unless the user specifies otherwise, new MCP servers should be created in: /mock/mcp/path +Unless the user specifies otherwise, new local MCP servers should be created in: /mock/mcp/path + +### MCP Server Types and Configuration + +MCP servers can be configured in two ways in the MCP settings file: + +1. Local (Stdio) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "local-weather": { + "command": "node", + "args": ["/path/to/weather-server/build/index.js"], + "env": { + "OPENWEATHER_API_KEY": "your-api-key" + } + } + } +} +\`\`\` + +2. Remote (SSE) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "remote-weather": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key" + } + } + } +} +\`\`\` + +Common configuration options for both types: +- \`disabled\`: (optional) Set to true to temporarily disable the server +- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) +- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -### Example MCP Server +### Example Local MCP Server For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. -The following example demonstrates how to build an MCP server that provides weather data functionality. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) +The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) 1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: @@ -4814,7 +4855,10 @@ By waiting for and carefully considering the user's response after each tool use MCP SERVERS -The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. +The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types: + +1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output +2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS # Connected MCP Servers @@ -6025,7 +6069,10 @@ By waiting for and carefully considering the user's response after each tool use MCP SERVERS -The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. +The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types: + +1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output +2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS # Connected MCP Servers @@ -6039,13 +6086,51 @@ The user may ask you something along the lines of "add a tool" that does some fu When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). -Unless the user specifies otherwise, new MCP servers should be created in: /mock/mcp/path +Unless the user specifies otherwise, new local MCP servers should be created in: /mock/mcp/path + +### MCP Server Types and Configuration + +MCP servers can be configured in two ways in the MCP settings file: + +1. Local (Stdio) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "local-weather": { + "command": "node", + "args": ["/path/to/weather-server/build/index.js"], + "env": { + "OPENWEATHER_API_KEY": "your-api-key" + } + } + } +} +\`\`\` + +2. Remote (SSE) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "remote-weather": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key" + } + } + } +} +\`\`\` + +Common configuration options for both types: +- \`disabled\`: (optional) Set to true to temporarily disable the server +- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) +- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -### Example MCP Server +### Example Local MCP Server For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. -The following example demonstrates how to build an MCP server that provides weather data functionality. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) +The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) 1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 3f7ec88297c..530bb374f7d 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -49,7 +49,10 @@ export async function getMcpServersSection( const baseSection = `MCP SERVERS -The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities. +The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types: + +1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output +2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS # Connected MCP Servers @@ -71,13 +74,51 @@ The user may ask you something along the lines of "add a tool" that does some fu When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration). -Unless the user specifies otherwise, new MCP servers should be created in: ${await mcpHub.getMcpServersPath()} +Unless the user specifies otherwise, new local MCP servers should be created in: ${await mcpHub.getMcpServersPath()} + +### MCP Server Types and Configuration + +MCP servers can be configured in two ways in the MCP settings file: + +1. Local (Stdio) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "local-weather": { + "command": "node", + "args": ["/path/to/weather-server/build/index.js"], + "env": { + "OPENWEATHER_API_KEY": "your-api-key" + } + } + } +} +\`\`\` + +2. Remote (SSE) Server Configuration: +\`\`\`json +{ + "mcpServers": { + "remote-weather": { + "url": "https://api.example.com/mcp", + "headers": { + "Authorization": "Bearer your-api-key" + } + } + } +} +\`\`\` + +Common configuration options for both types: +- \`disabled\`: (optional) Set to true to temporarily disable the server +- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) +- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -### Example MCP Server +### Example Local MCP Server For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities. -The following example demonstrates how to build an MCP server that provides weather data functionality. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) +The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS) 1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory: diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 6c906c7cf89..c7576efe002 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -1,5 +1,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { StdioClientTransport, StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js" +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" +import ReconnectingEventSource from "reconnecting-eventsource" import { CallToolResultSchema, ListResourcesResultSchema, @@ -31,23 +33,68 @@ import { arePathsEqual } from "../../utils/path" export type McpConnection = { server: McpServer client: Client - transport: StdioClientTransport + transport: StdioClientTransport | SSEClientTransport } -// StdioServerParameters -const AlwaysAllowSchema = z.array(z.string()).default([]) - -export const StdioConfigSchema = z.object({ - command: z.string(), - args: z.array(z.string()).optional(), - env: z.record(z.string()).optional(), - alwaysAllow: AlwaysAllowSchema.optional(), +// Base configuration schema for common settings +const BaseConfigSchema = z.object({ disabled: z.boolean().optional(), timeout: z.number().min(1).max(3600).optional().default(60), + alwaysAllow: z.array(z.string()).default([]), }) +// Custom error messages for better user feedback +const typeErrorMessage = "Server type must be either 'stdio' or 'sse'" +const stdioFieldsErrorMessage = + "For 'stdio' type servers, you must provide a 'command' field and can optionally include 'args' and 'env'" +const sseFieldsErrorMessage = + "For 'sse' type servers, you must provide a 'url' field and can optionally include 'headers'" +const mixedFieldsErrorMessage = + "Cannot mix 'stdio' and 'sse' fields. For 'stdio' use 'command', 'args', and 'env'. For 'sse' use 'url' and 'headers'" +const missingFieldsErrorMessage = "Server configuration must include either 'command' (for stdio) or 'url' (for sse)" + +// Helper function to create a refined schema with better error messages +const createServerTypeSchema = () => { + return z.union([ + // Stdio config (has command field) + BaseConfigSchema.extend({ + type: z.enum(["stdio"]).optional(), + command: z.string().min(1, "Command cannot be empty"), + args: z.array(z.string()).optional(), + env: z.record(z.string()).optional(), + // Ensure no SSE fields are present + url: z.undefined().optional(), + headers: z.undefined().optional(), + }) + .transform((data) => ({ + ...data, + type: "stdio" as const, + })) + .refine((data) => data.type === undefined || data.type === "stdio", { message: typeErrorMessage }), + // SSE config (has url field) + BaseConfigSchema.extend({ + type: z.enum(["sse"]).optional(), + url: z.string().url("URL must be a valid URL format"), + headers: z.record(z.string()).optional(), + // Ensure no stdio fields are present + command: z.undefined().optional(), + args: z.undefined().optional(), + env: z.undefined().optional(), + }) + .transform((data) => ({ + ...data, + type: "sse" as const, + })) + .refine((data) => data.type === undefined || data.type === "sse", { message: typeErrorMessage }), + ]) +} + +// Server configuration schema with automatic type inference and validation +export const ServerConfigSchema = createServerTypeSchema() + +// Settings schema const McpSettingsSchema = z.object({ - mcpServers: z.record(StdioConfigSchema), + mcpServers: z.record(ServerConfigSchema), }) export class McpHub { @@ -55,6 +102,7 @@ export class McpHub { private disposables: vscode.Disposable[] = [] private settingsWatcher?: vscode.FileSystemWatcher private fileWatchers: Map = new Map() + private isDisposed: boolean = false connections: McpConnection[] = [] isConnecting: boolean = false @@ -64,6 +112,76 @@ export class McpHub { this.initializeMcpServers() } + /** + * Validates and normalizes server configuration + * @param config The server configuration to validate + * @param serverName Optional server name for error messages + * @returns The validated configuration + * @throws Error if the configuration is invalid + */ + private validateServerConfig(config: any, serverName?: string): z.infer { + // Detect configuration issues before validation + const hasStdioFields = config.command !== undefined + const hasSseFields = config.url !== undefined + + // Check for mixed fields + if (hasStdioFields && hasSseFields) { + throw new Error(mixedFieldsErrorMessage) + } + + // Check if it's a stdio or SSE config and add type if missing + if (!config.type) { + if (hasStdioFields) { + config.type = "stdio" + } else if (hasSseFields) { + config.type = "sse" + } else { + throw new Error(missingFieldsErrorMessage) + } + } else if (config.type !== "stdio" && config.type !== "sse") { + throw new Error(typeErrorMessage) + } + + // Check for type/field mismatch + if (config.type === "stdio" && !hasStdioFields) { + throw new Error(stdioFieldsErrorMessage) + } + if (config.type === "sse" && !hasSseFields) { + throw new Error(sseFieldsErrorMessage) + } + + // Validate the config against the schema + try { + return ServerConfigSchema.parse(config) + } catch (validationError) { + if (validationError instanceof z.ZodError) { + // Extract and format validation errors + const errorMessages = validationError.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("; ") + throw new Error( + serverName + ? `Invalid configuration for server "${serverName}": ${errorMessages}` + : `Invalid server configuration: ${errorMessages}`, + ) + } + throw validationError + } + } + + /** + * Formats and displays error messages to the user + * @param message The error message prefix + * @param error The error object + */ + private showErrorMessage(message: string, error: unknown): void { + const errorMessage = error instanceof Error ? error.message : `${error}` + console.error(`${message}:`, error) + // if (vscode.window && typeof vscode.window.showErrorMessage === 'function') { + // vscode.window.showErrorMessage(`${message}: ${errorMessage}`) + // } + } + getServers(): McpServer[] { // Only return enabled servers return this.connections.filter((conn) => !conn.server.disabled).map((conn) => conn.server) @@ -113,7 +231,7 @@ export class McpHub { if (arePathsEqual(document.uri.fsPath, settingsPath)) { const content = await fs.readFile(settingsPath, "utf-8") const errorMessage = - "Invalid MCP settings format. Please ensure your settings follow the correct JSON format." + "Invalid MCP settings JSON format. Please ensure your settings follow the correct JSON format." let config: any try { config = JSON.parse(content) @@ -123,13 +241,16 @@ export class McpHub { } const result = McpSettingsSchema.safeParse(config) if (!result.success) { - vscode.window.showErrorMessage(errorMessage) + const errorMessages = result.error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n") + vscode.window.showErrorMessage(`Invalid MCP settings format: ${errorMessages}`) return } try { await this.updateServerConnections(result.data.mcpServers || {}) } catch (error) { - console.error("Failed to process MCP settings change:", error) + this.showErrorMessage("Failed to process MCP settings change", error) } } }), @@ -140,19 +261,47 @@ export class McpHub { try { const settingsPath = await this.getMcpSettingsFilePath() const content = await fs.readFile(settingsPath, "utf-8") - const config = JSON.parse(content) - await this.updateServerConnections(config.mcpServers || {}) + let config: any + + try { + config = JSON.parse(content) + } catch (parseError) { + const errorMessage = + "Invalid MCP settings JSON format. Please check your settings file for syntax errors." + console.error(errorMessage, parseError) + vscode.window.showErrorMessage(errorMessage) + return + } + + // Validate the config using McpSettingsSchema + const result = McpSettingsSchema.safeParse(config) + if (result.success) { + await this.updateServerConnections(result.data.mcpServers || {}) + } else { + // Format validation errors for better user feedback + const errorMessages = result.error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n") + console.error("Invalid MCP settings format:", errorMessages) + vscode.window.showErrorMessage(`Invalid MCP settings format: ${errorMessages}`) + + // Still try to connect with the raw config, but show warnings + try { + await this.updateServerConnections(config.mcpServers || {}) + } catch (error) { + this.showErrorMessage("Failed to initialize MCP servers with raw config", error) + } + } } catch (error) { - console.error("Failed to initialize MCP servers:", error) + this.showErrorMessage("Failed to initialize MCP servers", error) } } - private async connectToServer(name: string, config: StdioServerParameters): Promise { - // Remove existing connection if it exists (should never happen, the connection should be deleted beforehand) - this.connections = this.connections.filter((conn) => conn.server.name !== name) + private async connectToServer(name: string, config: z.infer): Promise { + // Remove existing connection if it exists + await this.deleteConnection(name) try { - // Each MCP server requires its own transport connection and has unique capabilities, configurations, and error handling. Having separate clients also allows proper scoping of resources/tools and independent server management like reconnection. const client = new Client( { name: "Roo Code", @@ -163,90 +312,103 @@ export class McpHub { }, ) - const transport = new StdioClientTransport({ - command: config.command, - args: config.args, - env: { - ...config.env, - ...(process.env.PATH ? { PATH: process.env.PATH } : {}), - // ...(process.env.NODE_PATH ? { NODE_PATH: process.env.NODE_PATH } : {}), - }, - stderr: "pipe", // necessary for stderr to be available - }) + let transport: StdioClientTransport | SSEClientTransport - transport.onerror = async (error) => { - console.error(`Transport error for "${name}":`, error) - const connection = this.connections.find((conn) => conn.server.name === name) - if (connection) { - connection.server.status = "disconnected" - this.appendErrorMessage(connection, error.message) + if (config.type === "stdio") { + transport = new StdioClientTransport({ + command: config.command, + args: config.args, + env: { + ...config.env, + ...(process.env.PATH ? { PATH: process.env.PATH } : {}), + }, + stderr: "pipe", + }) + + // Set up stdio specific error handling + transport.onerror = async (error) => { + console.error(`Transport error for "${name}":`, error) + const connection = this.connections.find((conn) => conn.server.name === name) + if (connection) { + connection.server.status = "disconnected" + this.appendErrorMessage(connection, error instanceof Error ? error.message : `${error}`) + } + await this.notifyWebviewOfServerChanges() } - await this.notifyWebviewOfServerChanges() - } - transport.onclose = async () => { - const connection = this.connections.find((conn) => conn.server.name === name) - if (connection) { - connection.server.status = "disconnected" + transport.onclose = async () => { + const connection = this.connections.find((conn) => conn.server.name === name) + if (connection) { + connection.server.status = "disconnected" + } + await this.notifyWebviewOfServerChanges() } - await this.notifyWebviewOfServerChanges() - } - // If the config is invalid, show an error - if (!StdioConfigSchema.safeParse(config).success) { - console.error(`Invalid config for "${name}": missing or invalid parameters`) - const connection: McpConnection = { - server: { - name, - config: JSON.stringify(config), - status: "disconnected", - error: "Invalid config: missing or invalid parameters", + // transport.stderr is only available after the process has been started. However we can't start it separately from the .connect() call because it also starts the transport. And we can't place this after the connect call since we need to capture the stderr stream before the connection is established, in order to capture errors during the connection process. + // As a workaround, we start the transport ourselves, and then monkey-patch the start method to no-op so that .connect() doesn't try to start it again. + await transport.start() + const stderrStream = transport.stderr + if (stderrStream) { + stderrStream.on("data", async (data: Buffer) => { + const errorOutput = data.toString() + console.error(`Server "${name}" stderr:`, errorOutput) + const connection = this.connections.find((conn) => conn.server.name === name) + if (connection) { + // NOTE: we do not set server status to "disconnected" because stderr logs do not necessarily mean the server crashed or disconnected, it could just be informational. In fact when the server first starts up, it immediately logs " server running on stdio" to stderr. + this.appendErrorMessage(connection, errorOutput) + // Only need to update webview right away if it's already disconnected + if (connection.server.status === "disconnected") { + await this.notifyWebviewOfServerChanges() + } + } + }) + } else { + console.error(`No stderr stream for ${name}`) + } + transport.start = async () => {} // No-op now, .connect() won't fail + } else { + // SSE connection + const sseOptions = { + requestInit: { + headers: config.headers, }, - client, - transport, } - this.connections.push(connection) - return + // Configure ReconnectingEventSource options + const reconnectingEventSourceOptions = { + max_retry_time: 5000, // Maximum retry time in milliseconds + withCredentials: config.headers?.["Authorization"] ? true : false, // Enable credentials if Authorization header exists + } + global.EventSource = ReconnectingEventSource + transport = new SSEClientTransport(new URL(config.url), { + ...sseOptions, + eventSourceInit: reconnectingEventSourceOptions, + }) + + // Set up SSE specific error handling + transport.onerror = async (error) => { + console.error(`Transport error for "${name}":`, error) + const connection = this.connections.find((conn) => conn.server.name === name) + if (connection) { + connection.server.status = "disconnected" + this.appendErrorMessage(connection, error instanceof Error ? error.message : `${error}`) + } + await this.notifyWebviewOfServerChanges() + } } - // valid schema - const parsedConfig = StdioConfigSchema.parse(config) const connection: McpConnection = { server: { name, config: JSON.stringify(config), status: "connecting", - disabled: parsedConfig.disabled, + disabled: config.disabled, }, client, transport, } this.connections.push(connection) - // transport.stderr is only available after the process has been started. However we can't start it separately from the .connect() call because it also starts the transport. And we can't place this after the connect call since we need to capture the stderr stream before the connection is established, in order to capture errors during the connection process. - // As a workaround, we start the transport ourselves, and then monkey-patch the start method to no-op so that .connect() doesn't try to start it again. - await transport.start() - const stderrStream = transport.stderr - if (stderrStream) { - stderrStream.on("data", async (data: Buffer) => { - const errorOutput = data.toString() - console.error(`Server "${name}" stderr:`, errorOutput) - const connection = this.connections.find((conn) => conn.server.name === name) - if (connection) { - // NOTE: we do not set server status to "disconnected" because stderr logs do not necessarily mean the server crashed or disconnected, it could just be informational. In fact when the server first starts up, it immediately logs " server running on stdio" to stderr. - this.appendErrorMessage(connection, errorOutput) - // Only need to update webview right away if it's already disconnected - if (connection.server.status === "disconnected") { - await this.notifyWebviewOfServerChanges() - } - } - }) - } else { - console.error(`No stderr stream for ${name}`) - } - transport.start = async () => {} // No-op now, .connect() won't fail - - // Connect + // Connect (this will automatically start the transport) await client.connect(transport) connection.server.status = "connected" connection.server.error = "" @@ -260,15 +422,20 @@ export class McpHub { const connection = this.connections.find((conn) => conn.server.name === name) if (connection) { connection.server.status = "disconnected" - this.appendErrorMessage(connection, error instanceof Error ? error.message : String(error)) + this.appendErrorMessage(connection, error instanceof Error ? error.message : `${error}`) } throw error } } private appendErrorMessage(connection: McpConnection, error: string) { + // Limit error message length to prevent excessive length + const maxErrorLength = 1000 const newError = connection.server.error ? `${connection.server.error}\n${error}` : error - connection.server.error = newError //.slice(0, 800) + connection.server.error = + newError.length > maxErrorLength + ? newError.substring(0, maxErrorLength) + "...(error message truncated)" + : newError } private async fetchToolsList(serverName: string): Promise { @@ -352,23 +519,32 @@ export class McpHub { for (const [name, config] of Object.entries(newServers)) { const currentConnection = this.connections.find((conn) => conn.server.name === name) + // Validate and transform the config + let validatedConfig: z.infer + try { + validatedConfig = this.validateServerConfig(config, name) + } catch (error) { + this.showErrorMessage(`Invalid configuration for MCP server "${name}"`, error) + continue + } + if (!currentConnection) { // New server try { - this.setupFileWatcher(name, config) - await this.connectToServer(name, config) + this.setupFileWatcher(name, validatedConfig) + await this.connectToServer(name, validatedConfig) } catch (error) { - console.error(`Failed to connect to new MCP server ${name}:`, error) + this.showErrorMessage(`Failed to connect to new MCP server ${name}`, error) } } else if (!deepEqual(JSON.parse(currentConnection.server.config), config)) { // Existing server with changed config try { - this.setupFileWatcher(name, config) + this.setupFileWatcher(name, validatedConfig) await this.deleteConnection(name) - await this.connectToServer(name, config) + await this.connectToServer(name, validatedConfig) console.log(`Reconnected MCP server with updated config: ${name}`) } catch (error) { - console.error(`Failed to reconnect MCP server ${name}:`, error) + this.showErrorMessage(`Failed to reconnect MCP server ${name}`, error) } } // If server exists with same config, do nothing @@ -377,22 +553,25 @@ export class McpHub { this.isConnecting = false } - private setupFileWatcher(name: string, config: any) { - const filePath = config.args?.find((arg: string) => arg.includes("build/index.js")) - if (filePath) { - // we use chokidar instead of onDidSaveTextDocument because it doesn't require the file to be open in the editor. The settings config is better suited for onDidSave since that will be manually updated by the user or Cline (and we want to detect save events, not every file change) - const watcher = chokidar.watch(filePath, { - // persistent: true, - // ignoreInitial: true, - // awaitWriteFinish: true, // This helps with atomic writes - }) - - watcher.on("change", () => { - console.log(`Detected change in ${filePath}. Restarting server ${name}...`) - this.restartConnection(name) - }) - - this.fileWatchers.set(name, watcher) + private setupFileWatcher(name: string, config: z.infer) { + // Only stdio type has args + if (config.type === "stdio") { + const filePath = config.args?.find((arg: string) => arg.includes("build/index.js")) + if (filePath) { + // we use chokidar instead of onDidSaveTextDocument because it doesn't require the file to be open in the editor. The settings config is better suited for onDidSave since that will be manually updated by the user or Cline (and we want to detect save events, not every file change) + const watcher = chokidar.watch(filePath, { + // persistent: true, + // ignoreInitial: true, + // awaitWriteFinish: true, // This helps with atomic writes + }) + + watcher.on("change", () => { + console.log(`Detected change in ${filePath}. Restarting server ${name}...`) + this.restartConnection(name) + }) + + this.fileWatchers.set(name, watcher) + } } } @@ -419,12 +598,20 @@ export class McpHub { await delay(500) // artificial delay to show user that server is restarting try { await this.deleteConnection(serverName) - // Try to connect again using existing config - await this.connectToServer(serverName, JSON.parse(config)) - vscode.window.showInformationMessage(`${serverName} MCP server connected`) + // Parse the config to validate it + const parsedConfig = JSON.parse(config) + try { + // Validate the config + const validatedConfig = this.validateServerConfig(parsedConfig, serverName) + + // Try to connect again using validated config + await this.connectToServer(serverName, validatedConfig) + vscode.window.showInformationMessage(`${serverName} MCP server connected`) + } catch (validationError) { + this.showErrorMessage(`Invalid configuration for MCP server "${serverName}"`, validationError) + } } catch (error) { - console.error(`Failed to restart connection for ${serverName}:`, error) - vscode.window.showErrorMessage(`Failed to connect to ${serverName} MCP server`) + this.showErrorMessage(`Failed to restart ${serverName} MCP server connection`, error) } } @@ -514,13 +701,7 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - console.error("Failed to update server disabled state:", error) - if (error instanceof Error) { - console.error("Error details:", error.message, error.stack) - } - vscode.window.showErrorMessage( - `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`, - ) + this.showErrorMessage(`Failed to update server ${serverName} state`, error) throw error } } @@ -567,13 +748,7 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - console.error("Failed to update server timeout:", error) - if (error instanceof Error) { - console.error("Error details:", error.message, error.stack) - } - vscode.window.showErrorMessage( - `Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`, - ) + this.showErrorMessage(`Failed to update server ${serverName} timeout settings`, error) throw error } } @@ -620,13 +795,7 @@ export class McpHub { vscode.window.showWarningMessage(`Server "${serverName}" not found in configuration`) } } catch (error) { - console.error("Failed to delete MCP server:", error) - if (error instanceof Error) { - console.error("Error details:", error.message, error.stack) - } - vscode.window.showErrorMessage( - `Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`, - ) + this.showErrorMessage(`Failed to delete MCP server ${serverName}`, error) throw error } } @@ -667,7 +836,7 @@ export class McpHub { let timeout: number try { - const parsedConfig = StdioConfigSchema.parse(JSON.parse(connection.server.config)) + const parsedConfig = ServerConfigSchema.parse(JSON.parse(connection.server.config)) timeout = (parsedConfig.timeout ?? 60) * 1000 } catch (error) { console.error("Failed to parse server config for timeout:", error) @@ -722,13 +891,13 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - console.error("Failed to update always allow settings:", error) - vscode.window.showErrorMessage("Failed to update always allow settings") + this.showErrorMessage(`Failed to update always allow settings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled } } async dispose(): Promise { + this.isDisposed = true this.removeAllFileWatchers() for (const connection of this.connections) { try { diff --git a/src/services/mcp/__tests__/McpHub.test.ts b/src/services/mcp/__tests__/McpHub.test.ts index b418bae21a7..737ed7f11e7 100644 --- a/src/services/mcp/__tests__/McpHub.test.ts +++ b/src/services/mcp/__tests__/McpHub.test.ts @@ -2,7 +2,7 @@ import type { McpHub as McpHubType } from "../McpHub" import type { ClineProvider } from "../../../core/webview/ClineProvider" import type { ExtensionContext, Uri } from "vscode" import type { McpConnection } from "../McpHub" -import { StdioConfigSchema } from "../McpHub" +import { ServerConfigSchema } from "../McpHub" const fs = require("fs/promises") const { McpHub } = require("../McpHub") @@ -71,6 +71,7 @@ describe("McpHub", () => { JSON.stringify({ mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], alwaysAllow: ["allowed-tool"], @@ -87,6 +88,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], alwaysAllow: [], @@ -109,6 +111,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], alwaysAllow: ["existing-tool"], @@ -131,6 +134,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], }, @@ -155,6 +159,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], disabled: false, @@ -294,20 +299,21 @@ describe("McpHub", () => { it("should validate timeout values", () => { // Test valid timeout values const validConfig = { + type: "stdio", command: "test", timeout: 60, } - expect(() => StdioConfigSchema.parse(validConfig)).not.toThrow() + expect(() => ServerConfigSchema.parse(validConfig)).not.toThrow() // Test invalid timeout values const invalidConfigs = [ - { command: "test", timeout: 0 }, // Too low - { command: "test", timeout: 3601 }, // Too high - { command: "test", timeout: -1 }, // Negative + { type: "stdio", command: "test", timeout: 0 }, // Too low + { type: "stdio", command: "test", timeout: 3601 }, // Too high + { type: "stdio", command: "test", timeout: -1 }, // Negative ] invalidConfigs.forEach((config) => { - expect(() => StdioConfigSchema.parse(config)).toThrow() + expect(() => ServerConfigSchema.parse(config)).toThrow() }) }) @@ -315,7 +321,7 @@ describe("McpHub", () => { const mockConnection: McpConnection = { server: { name: "test-server", - config: JSON.stringify({ command: "test" }), // No timeout specified + config: JSON.stringify({ type: "stdio", command: "test" }), // No timeout specified status: "connected", }, client: { @@ -338,7 +344,7 @@ describe("McpHub", () => { const mockConnection: McpConnection = { server: { name: "test-server", - config: JSON.stringify({ command: "test", timeout: 120 }), // 2 minutes + config: JSON.stringify({ type: "stdio", command: "test", timeout: 120 }), // 2 minutes status: "connected", }, client: { @@ -363,6 +369,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], timeout: 60, @@ -385,6 +392,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], timeout: 60, @@ -406,6 +414,7 @@ describe("McpHub", () => { server: { name: "test-server", config: JSON.stringify({ + type: "stdio", command: "node", args: ["test.js"], timeout: 3601, // Invalid timeout @@ -435,6 +444,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], timeout: 60, @@ -458,6 +468,7 @@ describe("McpHub", () => { const mockConfig = { mcpServers: { "test-server": { + type: "stdio", command: "node", args: ["test.js"], timeout: 60,