mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-18 21:29:41 -04:00
feat: Add basic response pane for MCP (#9131)
* New List page * Add a common utility function * Add response pane ui * Support events and logs
This commit is contained in:
86
package-lock.json
generated
86
package-lock.json
generated
@@ -4153,6 +4153,7 @@
|
||||
"version": "1.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.5.tgz",
|
||||
"integrity": "sha512-QakrKIGniGuRVfWBdMsDea/dx1PNE739QJ7gCM41s9q+qaCYTHCdsIBXQVVXry3mfWAiaM9kT22Hyz53Uw8mfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
@@ -4176,6 +4177,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "^3.0.0",
|
||||
@@ -4189,6 +4191,7 @@
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -4205,6 +4208,7 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
@@ -4225,6 +4229,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
@@ -4237,6 +4242,7 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
@@ -4246,6 +4252,7 @@
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
@@ -4288,6 +4295,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
@@ -4305,6 +4313,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -4314,12 +4323,14 @@
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -4329,6 +4340,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
|
||||
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4341,6 +4353,7 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
@@ -4353,6 +4366,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -4362,6 +4376,7 @@
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
@@ -4377,6 +4392,7 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
|
||||
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
@@ -4392,6 +4408,7 @@
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
@@ -4408,6 +4425,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.5",
|
||||
@@ -4430,6 +4448,7 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "^2.0.0",
|
||||
@@ -4445,6 +4464,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
@@ -11211,6 +11231,7 @@
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
@@ -11711,6 +11732,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
@@ -12145,6 +12167,7 @@
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
@@ -12169,6 +12192,7 @@
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
@@ -12178,6 +12202,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -12187,6 +12212,7 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
@@ -12199,6 +12225,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
@@ -12468,6 +12495,7 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -13457,6 +13485,7 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -13473,6 +13502,7 @@
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -13496,6 +13526,7 @@
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookies": {
|
||||
@@ -13569,6 +13600,7 @@
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
@@ -14238,6 +14270,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
@@ -14586,6 +14619,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
@@ -14974,6 +15008,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -15580,6 +15615,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
@@ -16067,6 +16103,7 @@
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -16111,6 +16148,7 @@
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
|
||||
"integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventsource-parser": "^3.0.1"
|
||||
@@ -16123,6 +16161,7 @@
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
|
||||
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -16169,6 +16208,7 @@
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
@@ -16225,6 +16265,7 @@
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
|
||||
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -16240,6 +16281,7 @@
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -16249,6 +16291,7 @@
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
@@ -16258,6 +16301,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -16267,6 +16311,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ext": {
|
||||
@@ -16557,6 +16602,7 @@
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
@@ -16575,6 +16621,7 @@
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
@@ -16584,6 +16631,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
@@ -16750,6 +16798,7 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -16780,6 +16829,7 @@
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -17789,6 +17839,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
@@ -17805,6 +17856,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -18263,6 +18315,7 @@
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
@@ -20384,6 +20437,7 @@
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -20413,6 +20467,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -20432,6 +20487,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -21032,6 +21088,7 @@
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -22195,6 +22252,7 @@
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
@@ -22491,6 +22549,7 @@
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -22648,6 +22707,7 @@
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
@@ -22795,6 +22855,7 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
|
||||
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
@@ -23383,6 +23444,7 @@
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
@@ -23449,6 +23511,7 @@
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
@@ -23522,6 +23585,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -23531,6 +23595,7 @@
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
@@ -23546,6 +23611,7 @@
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
@@ -24187,6 +24253,7 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
@@ -24203,6 +24270,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -24212,12 +24280,14 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/router/node_modules/path-to-regexp": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -24442,6 +24512,7 @@
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
@@ -24466,6 +24537,7 @@
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
@@ -24475,12 +24547,14 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send/node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -24490,6 +24564,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -24499,6 +24574,7 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
@@ -24551,6 +24627,7 @@
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
@@ -25317,6 +25394,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -26576,6 +26654,7 @@
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
@@ -26824,6 +26903,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -26972,6 +27052,7 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
@@ -27076,6 +27157,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@@ -28470,6 +28552,7 @@
|
||||
"version": "3.25.75",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz",
|
||||
"integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
@@ -28479,6 +28562,7 @@
|
||||
"version": "3.24.6",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
|
||||
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.1"
|
||||
@@ -28496,7 +28580,6 @@
|
||||
"@getinsomnia/node-libcurl": "3.0.0",
|
||||
"@grpc/grpc-js": "^1.13.3",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"@segment/analytics-node": "2.2.1",
|
||||
"@stoplight/spectral-core": "^1.20.0",
|
||||
@@ -28559,6 +28642,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@getinsomnia/api-client": "0.0.10",
|
||||
"@getinsomnia/srp-js": "1.0.0-alpha.1",
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@react-router/dev": "^7.7.0",
|
||||
"@react-router/fs-routes": "^7.7.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
|
||||
108
packages/insomnia/src/common/mcp-utils.ts
Normal file
108
packages/insomnia/src/common/mcp-utils.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
CallToolResultSchema,
|
||||
GetPromptRequestSchema,
|
||||
GetPromptResultSchema,
|
||||
InitializeRequestSchema,
|
||||
InitializeResultSchema,
|
||||
type JSONRPCMessage,
|
||||
ListPromptsRequestSchema,
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesRequestSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ListToolsRequestSchema,
|
||||
ListToolsResultSchema,
|
||||
type Prompt,
|
||||
ReadResourceRequestSchema,
|
||||
ReadResourceResultSchema,
|
||||
type Resource,
|
||||
type ResourceTemplate,
|
||||
type ServerCapabilities,
|
||||
type Tool,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
// method constants
|
||||
export const METHOD_INITIALIZE = InitializeRequestSchema.shape.method.value;
|
||||
export const METHOD_LIST_TOOLS = ListToolsRequestSchema.shape.method.value;
|
||||
export const METHOD_LIST_RESOURCES = ListResourcesRequestSchema.shape.method.value;
|
||||
export const METHOD_LIST_RESOURCE_TEMPLATES = ListResourceTemplatesRequestSchema.shape.method.value;
|
||||
export const METHOD_LIST_PROMPTS = ListPromptsRequestSchema.shape.method.value;
|
||||
export const METHOD_CALL_TOOL = CallToolRequestSchema.shape.method.value;
|
||||
export const METHOD_READ_RESOURCE = ReadResourceRequestSchema.shape.method.value;
|
||||
export const METHOD_GET_PROMPT = GetPromptRequestSchema.shape.method.value;
|
||||
const METHOD_UNKNOWN = 'unknown';
|
||||
export const MCP_JSONRPC_METHODS = [
|
||||
METHOD_INITIALIZE,
|
||||
METHOD_LIST_TOOLS,
|
||||
METHOD_LIST_RESOURCES,
|
||||
METHOD_LIST_RESOURCE_TEMPLATES,
|
||||
METHOD_LIST_PROMPTS,
|
||||
METHOD_CALL_TOOL,
|
||||
METHOD_READ_RESOURCE,
|
||||
METHOD_GET_PROMPT,
|
||||
];
|
||||
|
||||
export type JSONRPCMessageMethods =
|
||||
| typeof METHOD_INITIALIZE
|
||||
| typeof METHOD_LIST_TOOLS
|
||||
| typeof METHOD_LIST_RESOURCES
|
||||
| typeof METHOD_LIST_RESOURCE_TEMPLATES
|
||||
| typeof METHOD_LIST_PROMPTS
|
||||
| typeof METHOD_CALL_TOOL
|
||||
| typeof METHOD_READ_RESOURCE
|
||||
| typeof METHOD_GET_PROMPT;
|
||||
|
||||
export interface McpServerData {
|
||||
serverCapabilities: ServerCapabilities;
|
||||
primitives: {
|
||||
tools: Tool[];
|
||||
resources: Resource[];
|
||||
resourceTemplates: ResourceTemplate[];
|
||||
prompts: Prompt[];
|
||||
};
|
||||
}
|
||||
|
||||
export const getMcpMethodFromMessage = (message: JSONRPCMessage): JSONRPCMessageMethods | typeof METHOD_UNKNOWN => {
|
||||
let method: JSONRPCMessageMethods | typeof METHOD_UNKNOWN = METHOD_UNKNOWN;
|
||||
if ('result' in message) {
|
||||
const messageResult = message.result;
|
||||
if (InitializeResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_INITIALIZE;
|
||||
} else if (ListToolsResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_LIST_TOOLS;
|
||||
} else if (ListResourcesResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_LIST_RESOURCES;
|
||||
} else if (ListResourceTemplatesResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_LIST_RESOURCE_TEMPLATES;
|
||||
} else if (ListPromptsResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_LIST_PROMPTS;
|
||||
} else if (CallToolResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_CALL_TOOL;
|
||||
} else if (ReadResourceResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_READ_RESOURCE;
|
||||
} else if (GetPromptResultSchema.safeParse(messageResult).success) {
|
||||
method = METHOD_GET_PROMPT;
|
||||
}
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
export const getDefaultServerCapabilities = () => {
|
||||
return {
|
||||
tools: {
|
||||
enabled: false,
|
||||
listChanged: false,
|
||||
},
|
||||
resources: {
|
||||
enabled: false,
|
||||
listChanged: false,
|
||||
subscribe: true,
|
||||
},
|
||||
prompts: {
|
||||
enabled: false,
|
||||
listChanged: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -52,7 +52,7 @@ const socketIO: SocketIOBridgeAPI = {
|
||||
|
||||
const mcp: McpBridgeAPI = {
|
||||
connect: options => ipcRenderer.invoke('mcp.connect', options),
|
||||
close: options => ipcRenderer.send('mcp.close', options),
|
||||
close: options => ipcRenderer.invoke('mcp.close', options),
|
||||
closeAll: () => ipcRenderer.send('mcp.closeAll'),
|
||||
primitive: {
|
||||
listTools: options => ipcRenderer.invoke('mcp.primitive.listTools', options),
|
||||
@@ -70,7 +70,6 @@ const mcp: McpBridgeAPI = {
|
||||
event: {
|
||||
findMany: options => ipcRenderer.invoke('mcp.event.findMany', options),
|
||||
},
|
||||
getServerData: options => ipcRenderer.invoke('mcp.getServerData', options),
|
||||
};
|
||||
|
||||
const grpc: gRPCBridgeAPI = {
|
||||
|
||||
@@ -105,7 +105,8 @@ export type HandleChannels =
|
||||
| 'mcp.primitive.readResource'
|
||||
| 'mcp.primitive.subscribeResource'
|
||||
| 'mcp.readyState'
|
||||
| 'mcp.getServerData';
|
||||
| 'mcp.event.findMany'
|
||||
| 'mcp.close';
|
||||
|
||||
export const ipcMainHandle = (
|
||||
channel: HandleChannels,
|
||||
@@ -148,7 +149,6 @@ export type MainOnChannels =
|
||||
| 'updateLatestStepName'
|
||||
| 'webSocket.close'
|
||||
| 'webSocket.closeAll'
|
||||
| 'mcp.close'
|
||||
| 'mcp.closeAll'
|
||||
| 'mcp.sendMCPRequest'
|
||||
| 'writeText';
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import type {
|
||||
ClientRequest,
|
||||
Prompt,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
ServerCapabilities,
|
||||
Tool,
|
||||
import {
|
||||
type ClientRequest,
|
||||
isInitializeRequest,
|
||||
type JSONRPCMessage,
|
||||
type JSONRPCResponse,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import electron, { BrowserWindow } from 'electron';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { getAppVersion, getProductName } from '~/common/constants';
|
||||
import { getMcpMethodFromMessage } from '~/common/mcp-utils';
|
||||
import { generateId } from '~/common/misc';
|
||||
import * as models from '~/models';
|
||||
import type { TransportType } from '~/models/mcp-request';
|
||||
import type { McpResponse } from '~/models/mcp-response';
|
||||
import type { RequestAuthentication, RequestHeader } from '~/models/request';
|
||||
import { getBasicAuthHeader } from '~/network/basic-auth/get-header';
|
||||
import { getBearerAuthHeader } from '~/network/bearer-auth/get-header';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
|
||||
import { ipcMainHandle, ipcMainOn } from '../ipc/electron';
|
||||
|
||||
// Refer the SDK: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/shared/protocol.ts#L504
|
||||
// The Client type has missing transport property
|
||||
type McpClient = Client & { transport: StreamableHTTPClientTransport };
|
||||
// Mcp connection and request options
|
||||
interface CommonMcpOptions {
|
||||
requestId: string;
|
||||
}
|
||||
export interface OpenMcpClientConnectionOptions extends CommonMcpOptions {
|
||||
url: string;
|
||||
requestId: string;
|
||||
workspaceId: string;
|
||||
transportType: TransportType;
|
||||
headers: RequestHeader[];
|
||||
authentication: RequestAuthentication;
|
||||
@@ -39,21 +51,56 @@ interface CallToolOptions extends CommonMcpOptions {
|
||||
name: string;
|
||||
parameters: Record<string, any>;
|
||||
}
|
||||
export interface McpServerData {
|
||||
serverCapabilities: ServerCapabilities;
|
||||
primitives: {
|
||||
tools: Tool[];
|
||||
resources: Resource[];
|
||||
resourceTemplates: ResourceTemplate[];
|
||||
prompts: Prompt[];
|
||||
};
|
||||
|
||||
interface McpCloseEvent {
|
||||
_id: string;
|
||||
requestId: string;
|
||||
type: 'close';
|
||||
timestamp: number;
|
||||
reason: string;
|
||||
}
|
||||
export interface McpMessageEvent {
|
||||
_id: string;
|
||||
requestId: string;
|
||||
type: 'message';
|
||||
direction: 'INCOMING';
|
||||
timestamp: number;
|
||||
data: JSONRPCResponse;
|
||||
method: string;
|
||||
}
|
||||
interface McpErrorEvent {
|
||||
_id: string;
|
||||
requestId: string;
|
||||
timestamp: number;
|
||||
type: 'error';
|
||||
message: string;
|
||||
error: any;
|
||||
}
|
||||
interface McpRequestEvent {
|
||||
_id: string;
|
||||
requestId: string;
|
||||
type: 'message';
|
||||
timestamp: number;
|
||||
direction: 'OUTGOING';
|
||||
method: string;
|
||||
data: any;
|
||||
}
|
||||
export type McpEvent = McpMessageEvent | McpRequestEvent | McpCloseEvent | McpErrorEvent;
|
||||
interface ResponseEventOptions {
|
||||
responseId: string;
|
||||
requestId: string;
|
||||
environmentId: string | null;
|
||||
timelinePath: string;
|
||||
eventLogPath: string;
|
||||
}
|
||||
|
||||
const mcpConnections = new Map<string, Client>();
|
||||
// In-memory store of mcp server capabilities, tools/resources/resource templates/prompts list data for each mcp request
|
||||
const mcpServerDataStore = new Map<string, McpServerData>();
|
||||
const mcpConnections = new Map<string, McpClient>();
|
||||
const eventLogFileStreams = new Map<string, fs.WriteStream>();
|
||||
const timelineFileStreams = new Map<string, fs.WriteStream>();
|
||||
|
||||
const mcpStateChannelBuilder = (requestId: string) => `mcp.${requestId}.readyState`;
|
||||
const protocol = 'mcp';
|
||||
const getMcpStateChannel = (requestId: string) => `${protocol}.${requestId}.readyState`;
|
||||
const mcpEventIdGenerator = () => `mcp-${uuidV4()}`;
|
||||
const _getMcpClient = (id: string) => {
|
||||
const mcpClient = mcpConnections.get(id);
|
||||
if (!mcpClient) {
|
||||
@@ -68,13 +115,185 @@ const _notifyMcpClientStateChange = (channel: string, isConnected: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
const _clearMcpMaps = (requestId: string) => {
|
||||
const _clearMcpMaps = (requestId: string, timelineMessage: string, event?: McpEvent) => {
|
||||
if (event) {
|
||||
eventLogFileStreams.get(requestId)?.write(JSON.stringify(event) + '\n');
|
||||
}
|
||||
eventLogFileStreams.get(requestId)?.end();
|
||||
eventLogFileStreams.delete(requestId);
|
||||
timelineFileStreams
|
||||
.get(requestId)
|
||||
?.write(JSON.stringify({ value: timelineMessage, name: 'Text', timestamp: Date.now() }) + '\n');
|
||||
timelineFileStreams.get(requestId)?.end();
|
||||
timelineFileStreams.delete(requestId);
|
||||
mcpConnections.delete(requestId);
|
||||
mcpServerDataStore.delete(requestId);
|
||||
};
|
||||
|
||||
const _handleCloseMcpConnection = (requestId: string, error?: Error) => {
|
||||
if (error) {
|
||||
const closeEvent: McpErrorEvent = {
|
||||
_id: mcpEventIdGenerator(),
|
||||
requestId,
|
||||
type: 'error',
|
||||
timestamp: Date.now(),
|
||||
error,
|
||||
message: error.message || 'Unknown error',
|
||||
};
|
||||
// clear in-memory store
|
||||
_clearMcpMaps(requestId, 'Closed MCP connection', closeEvent);
|
||||
} else {
|
||||
const closeEvent: McpCloseEvent = {
|
||||
_id: mcpEventIdGenerator(),
|
||||
requestId,
|
||||
type: 'close',
|
||||
timestamp: Date.now(),
|
||||
reason: 'Mcp connection closed',
|
||||
};
|
||||
// clear in-memory store
|
||||
_clearMcpMaps(requestId, 'Closed MCP connection', closeEvent);
|
||||
}
|
||||
|
||||
const mcpStateChannel = getMcpStateChannel(requestId);
|
||||
// notify renderer process about state change
|
||||
_notifyMcpClientStateChange(mcpStateChannel, false);
|
||||
};
|
||||
|
||||
const _handleMcpConnectionError = (requestId: string, error: Error) => {
|
||||
const messageEvent: McpErrorEvent = {
|
||||
_id: mcpEventIdGenerator(),
|
||||
requestId,
|
||||
type: 'error',
|
||||
message: error.message || 'Unknown error',
|
||||
error,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
eventLogFileStreams.get(requestId)?.write(JSON.stringify(messageEvent) + '\n');
|
||||
console.error(`MCP connection error for requestId: ${requestId}`, error);
|
||||
// _handleCloseMcpConnection(requestId);
|
||||
};
|
||||
|
||||
const _handleMcpMessage = (message: JSONRPCMessage, requestId: string) => {
|
||||
const method = getMcpMethodFromMessage(message);
|
||||
const messageEvent: McpMessageEvent = {
|
||||
_id: mcpEventIdGenerator(),
|
||||
requestId,
|
||||
type: 'message',
|
||||
method,
|
||||
data: message as JSONRPCResponse,
|
||||
direction: 'INCOMING',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
eventLogFileStreams.get(requestId)?.write(JSON.stringify(messageEvent) + '\n');
|
||||
};
|
||||
|
||||
const createErrorResponse = async ({
|
||||
requestId,
|
||||
responseId,
|
||||
environmentId,
|
||||
timelinePath,
|
||||
message,
|
||||
}: ResponseEventOptions & { message: string }) => {
|
||||
const settings = await models.settings.get();
|
||||
const responsePatch = {
|
||||
_id: responseId,
|
||||
parentId: requestId,
|
||||
environmentId: environmentId,
|
||||
timelinePath,
|
||||
statusMessage: 'Error',
|
||||
error: message,
|
||||
};
|
||||
const res = await models.mcpResponse.create(responsePatch, settings.maxHistoryResponses);
|
||||
models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: res._id });
|
||||
};
|
||||
|
||||
const getInitialTimeline = (url: string) => {
|
||||
return [
|
||||
{ value: `Preparing request to ${url}`, name: 'Text', timestamp: Date.now() },
|
||||
{ value: `Current time is ${new Date().toISOString()}`, name: 'Text', timestamp: Date.now() },
|
||||
];
|
||||
};
|
||||
const parseResponseAndBuildTimeline = (requestHeaderLogs: string, response: Response) => {
|
||||
const statusMessage = response.statusText || '';
|
||||
const statusCode = response.status || 0;
|
||||
const responseHeaders: { name: string; value: string }[] = [...response.headers.entries()].map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
|
||||
const headersIn = responseHeaders.map(({ name, value }) => `${name}: ${value}`).join('\n');
|
||||
const timeline = [
|
||||
{ value: requestHeaderLogs, name: 'HeaderOut', timestamp: Date.now() },
|
||||
{ value: `${statusCode} ${statusMessage}`, name: 'HeaderIn', timestamp: Date.now() },
|
||||
{ value: headersIn, name: 'HeaderIn', timestamp: Date.now() },
|
||||
];
|
||||
return { timeline, responseHeaders, statusCode, statusMessage };
|
||||
};
|
||||
|
||||
// A wrapper fetch to log request and response details
|
||||
const fetchWithLogging = async (
|
||||
url: string | URL,
|
||||
init: RequestInit,
|
||||
{ requestId, responseId, environmentId, timelinePath, eventLogPath }: ResponseEventOptions,
|
||||
) => {
|
||||
const { method = 'GET' } = init;
|
||||
const reqHeader = new Headers(init?.headers || {});
|
||||
const isJsonRequest = reqHeader.get('content-type')?.toLowerCase().includes('application/json');
|
||||
const requestBody = isJsonRequest ? JSON.parse(init.body?.toString() || '{}') : init.body?.toString() || '';
|
||||
const isMcpInitializeRequest = isJsonRequest && isInitializeRequest(requestBody);
|
||||
if (isMcpInitializeRequest) {
|
||||
// Add initial timeline
|
||||
const initialTimelines = getInitialTimeline(url.toString());
|
||||
initialTimelines.map(t => timelineFileStreams.get(requestId)?.write(JSON.stringify(t) + '\n'));
|
||||
}
|
||||
const requestHeaders: { name: string; value: string }[] = [...reqHeader.entries()].map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
const requestMethodLine = `${method.toUpperCase()} ${url} ${isJsonRequest && requestBody?.method ? `\nJSON-RPC Method: ${requestBody.method}` : ''}`;
|
||||
const headersOut = requestHeaders.map(({ name, value }) => `${name}: ${value}`).join('\n');
|
||||
const start = performance.now();
|
||||
const response = await fetch(url, init);
|
||||
const { timeline, responseHeaders, statusCode, statusMessage } = parseResponseAndBuildTimeline(
|
||||
`${requestMethodLine}\n${headersOut}`,
|
||||
response,
|
||||
);
|
||||
timeline.map(t => timelineFileStreams.get(requestId)?.write(JSON.stringify(t) + '\n'));
|
||||
if (isMcpInitializeRequest) {
|
||||
// Create response model only for initialize response
|
||||
const responsePatch: Partial<McpResponse> = {
|
||||
_id: responseId,
|
||||
parentId: requestId,
|
||||
environmentId,
|
||||
headers: responseHeaders,
|
||||
url: url.toString(),
|
||||
statusCode,
|
||||
statusMessage,
|
||||
elapsedTime: performance.now() - start,
|
||||
timelinePath,
|
||||
eventLogPath,
|
||||
};
|
||||
const settings = await models.settings.get();
|
||||
const res = await models.mcpResponse.create(responsePatch, settings.maxHistoryResponses);
|
||||
models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: res._id });
|
||||
}
|
||||
if (requestBody) {
|
||||
// Add request event
|
||||
const requestEvent: McpRequestEvent = {
|
||||
_id: mcpEventIdGenerator(),
|
||||
method: requestBody.method,
|
||||
requestId,
|
||||
type: 'message',
|
||||
direction: 'OUTGOING',
|
||||
timestamp: Date.now(),
|
||||
data: requestBody,
|
||||
};
|
||||
eventLogFileStreams.get(requestId)?.write(JSON.stringify(requestEvent) + '\n');
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
const openMcpClientConnection = async (options: OpenMcpClientConnectionOptions) => {
|
||||
const { transportType, url, requestId } = options;
|
||||
const { transportType, url, requestId, workspaceId } = options;
|
||||
if (!url) {
|
||||
throw new Error('MCP server url is required');
|
||||
}
|
||||
@@ -103,20 +322,47 @@ const openMcpClientConnection = async (options: OpenMcpClientConnectionOptions)
|
||||
.filter(({ name, disabled }) => Boolean(name) && !disabled)
|
||||
.reduce(reduceArrayToLowerCaseKeyedDictionary, {});
|
||||
|
||||
// create response model and file streams
|
||||
const responseId = generateId('res');
|
||||
const responsesDir = path.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), 'responses');
|
||||
const eventLogPath = path.join(responsesDir, uuidV4() + '.response');
|
||||
eventLogFileStreams.set(requestId, fs.createWriteStream(eventLogPath));
|
||||
const timelinePath = path.join(responsesDir, responseId + '.timeline');
|
||||
timelineFileStreams.set(requestId, fs.createWriteStream(timelinePath));
|
||||
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspaceId);
|
||||
// fallback to base environment
|
||||
const activeEnvironmentId = workspaceMeta.activeEnvironmentId;
|
||||
const activeEnvironment = activeEnvironmentId && (await models.environment.getById(activeEnvironmentId));
|
||||
const environment = activeEnvironment || (await models.environment.getOrCreateForParentId(workspaceId));
|
||||
invariant(environment, 'failed to find environment ' + activeEnvironmentId);
|
||||
const responseEnvironmentId = environment ? environment._id : null;
|
||||
|
||||
// create connection
|
||||
const mcpClient = new Client({
|
||||
name: getProductName(),
|
||||
version: getAppVersion(),
|
||||
});
|
||||
const mcpStateChannel = mcpStateChannelBuilder(requestId);
|
||||
mcpClient.onclose = () => _handleCloseMcpConnection(requestId);
|
||||
mcpClient.onerror = _error => _handleMcpConnectionError(requestId, _error);
|
||||
const mcpStateChannel = getMcpStateChannel(requestId);
|
||||
let transport: StreamableHTTPClientTransport;
|
||||
|
||||
switch (transportType) {
|
||||
case 'streamable-http': {
|
||||
try {
|
||||
const mcpServerUrl = new URL(url);
|
||||
const transport = new StreamableHTTPClientTransport(mcpServerUrl, {
|
||||
transport = new StreamableHTTPClientTransport(mcpServerUrl, {
|
||||
requestInit: {
|
||||
headers: lowerCasedEnabledHeaders,
|
||||
},
|
||||
fetch: (url, init) =>
|
||||
fetchWithLogging(url, init || {}, {
|
||||
requestId,
|
||||
responseId,
|
||||
environmentId: responseEnvironmentId,
|
||||
timelinePath,
|
||||
eventLogPath,
|
||||
}),
|
||||
reconnectionOptions: {
|
||||
maxReconnectionDelay: 30000,
|
||||
initialReconnectionDelay: 1000,
|
||||
@@ -124,73 +370,56 @@ const openMcpClientConnection = async (options: OpenMcpClientConnectionOptions)
|
||||
maxRetries: 2,
|
||||
},
|
||||
});
|
||||
transport.onmessage = message => _handleMcpMessage(message, requestId);
|
||||
await mcpClient.connect(transport);
|
||||
mcpClient.onclose = () => {
|
||||
// terminate the session when client is closed
|
||||
transport.terminateSession();
|
||||
// clear in-memory store
|
||||
_clearMcpMaps(requestId);
|
||||
// notify renderer process about state change
|
||||
_notifyMcpClientStateChange(mcpStateChannel, false);
|
||||
};
|
||||
mcpClient.onerror = _error => {
|
||||
// TODO support error
|
||||
};
|
||||
break;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create Streamable HTTP transport: ${error}`);
|
||||
// Log error when connection fails with exception
|
||||
createErrorResponse({
|
||||
requestId,
|
||||
responseId,
|
||||
environmentId: responseEnvironmentId,
|
||||
timelinePath,
|
||||
eventLogPath,
|
||||
message: error.message || 'Something went wrong',
|
||||
});
|
||||
console.error(`Failed to create Streamable HTTP transport: ${error}`);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unsupported transport type: ${transportType}`);
|
||||
}
|
||||
}
|
||||
|
||||
mcpConnections.set(requestId, mcpClient);
|
||||
mcpConnections.set(requestId, mcpClient as McpClient);
|
||||
const serverCapabilities = mcpClient.getServerCapabilities();
|
||||
let tools: Tool[] = [];
|
||||
let resources: Resource[] = [];
|
||||
let resourceTemplates: ResourceTemplate[] = [];
|
||||
let prompts: Prompt[] = [];
|
||||
const primitivePromises: Promise<any>[] = [];
|
||||
// get server primitives if supported
|
||||
if (serverCapabilities?.tools) {
|
||||
primitivePromises.push(mcpClient.listTools().then(response => (tools = response.tools)));
|
||||
primitivePromises.push(mcpClient.listTools());
|
||||
}
|
||||
if (serverCapabilities?.resources) {
|
||||
primitivePromises.push(mcpClient.listResources().then(response => (resources = response.resources)));
|
||||
primitivePromises.push(
|
||||
mcpClient.listResourceTemplates().then(response => (resourceTemplates = response.resourceTemplates)),
|
||||
);
|
||||
primitivePromises.push(mcpClient.listResources());
|
||||
primitivePromises.push(mcpClient.listResourceTemplates());
|
||||
}
|
||||
if (serverCapabilities?.prompts) {
|
||||
primitivePromises.push(mcpClient.listPrompts().then(response => (prompts = response.prompts)));
|
||||
primitivePromises.push(mcpClient.listPrompts());
|
||||
}
|
||||
try {
|
||||
await Promise.all(primitivePromises);
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch one or more primitive types from MCP server', error);
|
||||
}
|
||||
const serverData = {
|
||||
serverCapabilities: serverCapabilities,
|
||||
primitives: {
|
||||
tools,
|
||||
resources,
|
||||
resourceTemplates,
|
||||
prompts,
|
||||
},
|
||||
};
|
||||
mcpServerDataStore.set(requestId, serverData as McpServerData);
|
||||
// notify connection ready after capabilities and primitives are fetched
|
||||
_notifyMcpClientStateChange(mcpStateChannel, true);
|
||||
return serverData;
|
||||
};
|
||||
|
||||
const closeMcpConnection = (options: CommonMcpOptions) => {
|
||||
const closeMcpConnection = async (options: CommonMcpOptions) => {
|
||||
const { requestId } = options;
|
||||
const mcpClient = _getMcpClient(requestId);
|
||||
if (mcpClient) {
|
||||
await mcpClient.transport.terminateSession();
|
||||
mcpClient.close();
|
||||
}
|
||||
};
|
||||
@@ -201,10 +430,22 @@ const closeAllMcpConnections = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getServerData = async (options: CommonMcpOptions) => mcpServerDataStore.get(options.requestId);
|
||||
|
||||
const findMany = async (_options: { responseId: string }): Promise<any> => {
|
||||
return [];
|
||||
const findMany = async (options: { responseId: string }): Promise<McpEvent[]> => {
|
||||
const response = await models.mcpResponse.getById(options.responseId);
|
||||
if (!response || !response.eventLogPath) {
|
||||
return [];
|
||||
}
|
||||
const body = await fs.promises.readFile(response.eventLogPath);
|
||||
return (
|
||||
body
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(e => e?.trim())
|
||||
// Parse the message
|
||||
.map(e => JSON.parse(e))
|
||||
// Reverse the list of messages so that we get the latest message first
|
||||
.reverse() || []
|
||||
);
|
||||
};
|
||||
|
||||
const listTools = async (options: CommonMcpOptions) => {
|
||||
@@ -269,7 +510,6 @@ export interface McpBridgeAPI {
|
||||
connect: typeof openMcpClientConnection;
|
||||
close: typeof closeMcpConnection;
|
||||
closeAll: typeof closeAllMcpConnections;
|
||||
getServerData: typeof getServerData;
|
||||
primitive: {
|
||||
listTools: typeof listTools;
|
||||
callTool: typeof callTool;
|
||||
@@ -308,10 +548,10 @@ export const registerMcpHandlers = () => {
|
||||
ipcMainHandle('mcp.primitive.subscribeResource', (_, options: Parameters<typeof subscribeResource>[0]) =>
|
||||
subscribeResource(options),
|
||||
);
|
||||
ipcMainOn('mcp.close', (_, options: Parameters<typeof closeMcpConnection>[0]) => closeMcpConnection(options));
|
||||
ipcMainHandle('mcp.close', (_, options: Parameters<typeof closeMcpConnection>[0]) => closeMcpConnection(options));
|
||||
ipcMainOn('mcp.closeAll', closeAllMcpConnections);
|
||||
ipcMainHandle('mcp.readyState', (_, options: Parameters<typeof getMcpReadyState>[0]) => getMcpReadyState(options));
|
||||
ipcMainHandle('mcp.getServerData', (_, options: Parameters<typeof getMcpReadyState>[0]) => getServerData(options));
|
||||
ipcMainHandle('mcp.event.findMany', (_, options: Parameters<typeof findMany>[0]) => findMany(options));
|
||||
};
|
||||
|
||||
electron.app.on('window-all-closed', closeAllMcpConnections);
|
||||
|
||||
@@ -14,6 +14,8 @@ export const canSync = false;
|
||||
|
||||
export interface BaseMcpResponse {
|
||||
environmentId: string | null;
|
||||
statusCode: number;
|
||||
statusMessage: string;
|
||||
url: string;
|
||||
elapsedTime: number;
|
||||
headers: ResponseHeader[];
|
||||
@@ -37,6 +39,8 @@ export function init(): BaseMcpResponse {
|
||||
timelinePath: '',
|
||||
eventLogPath: '',
|
||||
error: '',
|
||||
statusCode: 0,
|
||||
statusMessage: '',
|
||||
requestVersionId: null,
|
||||
environmentId: null,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { compressObject, decompressObject } from '../common/misc';
|
||||
import * as requestOperations from '../models/helpers/request-operations';
|
||||
import type { GrpcRequest } from './grpc-request';
|
||||
import type { BaseModel } from './index';
|
||||
import type { McpRequest } from './mcp-request';
|
||||
import { isMcpRequest, type McpRequest } from './mcp-request';
|
||||
import { isRequest, type Request } from './request';
|
||||
import { isSocketIORequest, type SocketIORequest } from './socket-io-request';
|
||||
import { isWebSocketRequest, type WebSocketRequest } from './websocket-request';
|
||||
@@ -58,7 +58,7 @@ export function findByParentId(parentId: string) {
|
||||
}
|
||||
|
||||
export async function create(request: Request | WebSocketRequest | GrpcRequest | SocketIORequest | McpRequest) {
|
||||
if (!isRequest(request) && !isWebSocketRequest(request) && !isSocketIORequest(request)) {
|
||||
if (!isRequest(request) && !isWebSocketRequest(request) && !isSocketIORequest(request) && !isMcpRequest(request)) {
|
||||
throw new Error(`New ${type} was not given a valid ${request.type} instance`);
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ export async function restore(requestVersionId: string) {
|
||||
return requestOperations.update(originalRequest, requestPatch);
|
||||
}
|
||||
function _diffRequests(
|
||||
rOld: Request | WebSocketRequest | SocketIORequest | null,
|
||||
rNew: Request | WebSocketRequest | SocketIORequest,
|
||||
rOld: Request | WebSocketRequest | SocketIORequest | McpRequest | null,
|
||||
rNew: Request | WebSocketRequest | SocketIORequest | McpRequest,
|
||||
) {
|
||||
if (!rOld) {
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { href } from 'react-router';
|
||||
|
||||
import * as models from '~/models';
|
||||
import * as requestOperations from '~/models/helpers/request-operations';
|
||||
import { isMcpRequestId } from '~/models/mcp-request';
|
||||
import { isSocketIORequestId } from '~/models/socket-io-request';
|
||||
import { isWebSocketRequestId } from '~/models/websocket-request';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
@@ -22,6 +23,8 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
|
||||
await models.webSocketResponse.removeForRequest(requestId, workspaceMeta.activeEnvironmentId);
|
||||
} else if (isSocketIORequestId(requestId)) {
|
||||
await models.socketIOResponse.removeForRequest(requestId, workspaceMeta.activeEnvironmentId);
|
||||
} else if (isMcpRequestId(requestId)) {
|
||||
await models.mcpResponse.removeForRequest(requestId, workspaceMeta.activeEnvironmentId);
|
||||
} else {
|
||||
await models.response.removeForRequest(requestId, workspaceMeta.activeEnvironmentId);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ import { href } from 'react-router';
|
||||
|
||||
import * as models from '~/models';
|
||||
import * as requestOperations from '~/models/helpers/request-operations';
|
||||
import { isMcpRequestId } from '~/models/mcp-request';
|
||||
import type { McpResponse } from '~/models/mcp-response';
|
||||
import type { Response } from '~/models/response';
|
||||
import { isSocketIORequestId } from '~/models/socket-io-request';
|
||||
import type { SocketIOResponse } from '~/models/socket-io-response';
|
||||
import { isWebSocketRequestId } from '~/models/websocket-request';
|
||||
import type { WebSocketResponse } from '~/models/websocket-response';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
import { createFetcherSubmitHook } from '~/utils/router';
|
||||
|
||||
@@ -20,36 +25,42 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
|
||||
const workspaceMeta = await models.workspaceMeta.getByParentId(workspaceId);
|
||||
invariant(workspaceMeta, 'Active workspace meta not found');
|
||||
const isWebSocketRequest = isWebSocketRequestId(requestId);
|
||||
const isSocketIORequest = isSocketIORequestId(requestId);
|
||||
const isMcpRequest = isMcpRequestId(requestId);
|
||||
|
||||
if (isWebSocketRequestId(requestId)) {
|
||||
const res = await models.webSocketResponse.getById(responseId);
|
||||
invariant(res, 'Response not found');
|
||||
await models.webSocketResponse.remove(res);
|
||||
const response = await models.webSocketResponse.getLatestForRequestId(requestId, workspaceMeta.activeEnvironmentId);
|
||||
if (response?.requestVersionId) {
|
||||
await models.requestVersion.restore(response.requestVersionId);
|
||||
}
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: response?._id || null });
|
||||
} else if (isSocketIORequestId(requestId)) {
|
||||
const res = await models.socketIOResponse.getById(responseId);
|
||||
invariant(res, 'Response not found');
|
||||
await models.socketIOResponse.remove(res);
|
||||
const response = await models.socketIOResponse.getLatestForRequestId(requestId, workspaceMeta.activeEnvironmentId);
|
||||
if (response?.requestVersionId) {
|
||||
await models.requestVersion.restore(response.requestVersionId);
|
||||
}
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: response?._id || null });
|
||||
let responseModel;
|
||||
if (isWebSocketRequest) {
|
||||
responseModel = models.webSocketResponse;
|
||||
} else if (isSocketIORequest) {
|
||||
responseModel = models.socketIOResponse;
|
||||
} else if (isMcpRequest) {
|
||||
responseModel = models.mcpResponse;
|
||||
} else {
|
||||
const res = await models.response.getById(responseId);
|
||||
invariant(res, 'Response not found');
|
||||
await models.response.remove(res);
|
||||
const response = await models.response.getLatestForRequestId(requestId, workspaceMeta.activeEnvironmentId);
|
||||
if (response?.requestVersionId) {
|
||||
await models.requestVersion.restore(response.requestVersionId);
|
||||
}
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: response?._id || null });
|
||||
responseModel = models.response;
|
||||
}
|
||||
|
||||
const res = await responseModel.getById(responseId);
|
||||
invariant(res, 'Response not found');
|
||||
|
||||
// Type-safe remove operation based on the request type
|
||||
if (isWebSocketRequest) {
|
||||
await models.webSocketResponse.remove(res as WebSocketResponse);
|
||||
} else if (isSocketIORequest) {
|
||||
await models.socketIOResponse.remove(res as SocketIOResponse);
|
||||
} else if (isMcpRequest) {
|
||||
await models.mcpResponse.remove(res as McpResponse);
|
||||
} else {
|
||||
await models.response.remove(res as Response);
|
||||
}
|
||||
const response = await responseModel.getLatestForRequestId(requestId, workspaceMeta.activeEnvironmentId);
|
||||
if (response?.requestVersionId) {
|
||||
await models.requestVersion.restore(response.requestVersionId);
|
||||
}
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, {
|
||||
activeResponseId: response?._id || null,
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export async function clientAction({ params, request }: Route.ClientActionArgs)
|
||||
|
||||
window.main.mcp.connect({
|
||||
requestId,
|
||||
workspaceId,
|
||||
transportType: rendered.transportType || 'streamable-http',
|
||||
url: rendered.url,
|
||||
headers: rendered.headers,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Prompt, Resource, ResourceTemplate, Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
@@ -18,17 +17,35 @@ import { NavLink, redirect, useParams } from 'react-router';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
|
||||
import { DEFAULT_SIDEBAR_SIZE } from '~/common/constants';
|
||||
import type { McpServerData } from '~/main/network/mcp';
|
||||
import {
|
||||
getDefaultServerCapabilities,
|
||||
type McpServerData,
|
||||
METHOD_INITIALIZE,
|
||||
METHOD_LIST_PROMPTS,
|
||||
METHOD_LIST_RESOURCE_TEMPLATES,
|
||||
METHOD_LIST_RESOURCES,
|
||||
METHOD_LIST_TOOLS,
|
||||
} from '~/common/mcp-utils';
|
||||
import type { McpEvent, McpMessageEvent } from '~/main/network/mcp';
|
||||
import * as models from '~/models';
|
||||
import type { McpServerPrimitiveTypes } from '~/models/mcp-request';
|
||||
import type { McpRequest, McpServerPrimitiveTypes } from '~/models/mcp-request';
|
||||
import { useRootLoaderData } from '~/root';
|
||||
import { useWorkspaceLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId';
|
||||
import { useMcpRequestLoaderData } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
|
||||
import { McpActionsDropdown } from '~/ui/components/dropdowns/mcp-actions-dropdown';
|
||||
import { WorkspaceDropdown } from '~/ui/components/dropdowns/workspace-dropdown';
|
||||
import { EnvironmentPicker } from '~/ui/components/environment-picker';
|
||||
import { ErrorBoundary } from '~/ui/components/error-boundary';
|
||||
import { Icon } from '~/ui/components/icon';
|
||||
import { McpRequestPane } from '~/ui/components/mcp/mcp-request-pane';
|
||||
import {
|
||||
type PrimitiveSubItem,
|
||||
type PrimitiveTypeItem,
|
||||
type PromptItem,
|
||||
type ResourceItem,
|
||||
type ResourceTemplateItem,
|
||||
type ToolItem,
|
||||
} from '~/ui/components/mcp/types';
|
||||
import { WorkspaceEnvironmentsEditModal } from '~/ui/components/modals/workspace-environments-edit-modal';
|
||||
import { OrganizationTabList } from '~/ui/components/tabs/tab-list';
|
||||
import { McpRealtimeResponsePane } from '~/ui/components/websockets/realtime-response-pane';
|
||||
@@ -38,20 +55,6 @@ import { invariant } from '~/utils/invariant';
|
||||
|
||||
import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp';
|
||||
|
||||
interface CommonItemProps {
|
||||
itemLevel: number;
|
||||
hide: boolean;
|
||||
}
|
||||
type ToolItem = Tool & { type: 'tools' } & CommonItemProps;
|
||||
type ResourceItem = Resource & { type: 'resources' } & CommonItemProps;
|
||||
type ResourceTemplateItem = ResourceTemplate & { type: 'resources' } & CommonItemProps;
|
||||
type PromptItem = Prompt & { type: 'prompts' } & CommonItemProps;
|
||||
export type PrimitiveSubItemTypes = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
|
||||
interface PrimitiveTypeItem extends CommonItemProps {
|
||||
type: McpServerPrimitiveTypes;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
|
||||
if (!params.requestId) {
|
||||
const { projectId, workspaceId, organizationId } = params;
|
||||
@@ -78,7 +81,7 @@ const McpPage = () => {
|
||||
projectId: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
const { activeRequest } = useMcpRequestLoaderData()!;
|
||||
const { activeRequest, activeResponse } = useMcpRequestLoaderData()!;
|
||||
const sidebarPanelRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const [isEnvironmentPickerOpen, setIsEnvironmentPickerOpen] = useState(false);
|
||||
const [isEnvironmentModalOpen, setEnvironmentModalOpen] = useState(false);
|
||||
@@ -87,10 +90,10 @@ const McpPage = () => {
|
||||
const { settings } = useRootLoaderData()!;
|
||||
const [mcpServerData, setMcpServerData] = useState<McpServerData | null>(null);
|
||||
const [collapsedPrimitives, setCollapsedPrimitives] = useState<McpServerPrimitiveTypes[]>([]);
|
||||
const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState<PrimitiveSubItemTypes | null>(null);
|
||||
console.log('selectedPrimitiveItem', selectedPrimitiveItem);
|
||||
const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState<PrimitiveSubItem | null>(null);
|
||||
|
||||
const getPrimitiveCollection = () => {
|
||||
const collection: (PrimitiveTypeItem | PrimitiveSubItemTypes)[] = [];
|
||||
const collection: (PrimitiveTypeItem | PrimitiveSubItem)[] = [];
|
||||
if (mcpServerData) {
|
||||
const { primitives } = mcpServerData;
|
||||
const { tools, resources, resourceTemplates, prompts } = primitives;
|
||||
@@ -140,21 +143,7 @@ const McpPage = () => {
|
||||
};
|
||||
|
||||
const getServerCapabilities = () => {
|
||||
const serverCapabilities = {
|
||||
tools: {
|
||||
enabled: true,
|
||||
listChanged: false,
|
||||
},
|
||||
resources: {
|
||||
enabled: true,
|
||||
listChanged: false,
|
||||
subscribe: true,
|
||||
},
|
||||
prompts: {
|
||||
enabled: true,
|
||||
listChanged: false,
|
||||
},
|
||||
};
|
||||
const serverCapabilities = getDefaultServerCapabilities();
|
||||
if (mcpServerData) {
|
||||
const { tools, resources, prompts } = mcpServerData.serverCapabilities;
|
||||
if (tools) {
|
||||
@@ -183,10 +172,9 @@ const McpPage = () => {
|
||||
serverCapabilities.resources.listChanged ||
|
||||
serverCapabilities.prompts.listChanged;
|
||||
// TODO Use these variables to enable notification
|
||||
console.log('enableNotification', enableNotification);
|
||||
console.log('allowSubscribeResources', allowSubscribeResources);
|
||||
// TODO Use this for showing details
|
||||
console.log('selectedPrimitiveItem', selectedPrimitiveItem);
|
||||
console.log(`enableNotification`, enableNotification);
|
||||
console.log(`allowSubscribeResources`, allowSubscribeResources);
|
||||
|
||||
const requestId = activeRequest._id;
|
||||
const { activeEnvironment } = useWorkspaceLoaderData()!;
|
||||
const readyState = useReadyState({ requestId, protocol: 'mcp' });
|
||||
@@ -251,16 +239,42 @@ const McpPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const updateServerData = async () => {
|
||||
const serverData = await window.main.mcp.getServerData({ requestId });
|
||||
setMcpServerData(serverData!);
|
||||
const findFirstMatchEventData = (mcpEvents: McpEvent[], method: string) => {
|
||||
const firstMatchEvent = mcpEvents.find(
|
||||
event => 'method' in event && event.method === method,
|
||||
) as McpMessageEvent;
|
||||
if (firstMatchEvent) {
|
||||
return firstMatchEvent.data.result;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const activeResponseId = activeResponse?._id;
|
||||
if (activeResponseId) {
|
||||
const allEvents = await window.main.mcp.event.findMany({ responseId: activeResponseId });
|
||||
const serverCapabilities =
|
||||
findFirstMatchEventData(allEvents, METHOD_INITIALIZE)?.capabilities || getDefaultServerCapabilities();
|
||||
const tools = findFirstMatchEventData(allEvents, METHOD_LIST_TOOLS)?.tools || [];
|
||||
const resources = findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCES)?.resources || [];
|
||||
const resourceTemplates =
|
||||
findFirstMatchEventData(allEvents, METHOD_LIST_RESOURCE_TEMPLATES)?.resourceTemplates || [];
|
||||
const prompts = findFirstMatchEventData(allEvents, METHOD_LIST_PROMPTS)?.prompts || [];
|
||||
const mcpServerData = {
|
||||
serverCapabilities: serverCapabilities,
|
||||
primitives: {
|
||||
tools,
|
||||
resources,
|
||||
resourceTemplates,
|
||||
prompts,
|
||||
},
|
||||
} as McpServerData;
|
||||
setMcpServerData(mcpServerData);
|
||||
}
|
||||
};
|
||||
if (readyState) {
|
||||
// Get MCP server data when connection is ready
|
||||
updateServerData();
|
||||
} else {
|
||||
setMcpServerData(null);
|
||||
}
|
||||
}, [readyState, requestId]);
|
||||
}, [readyState, activeResponse?._id]);
|
||||
|
||||
return (
|
||||
<PanelGroup
|
||||
@@ -372,58 +386,22 @@ const McpPage = () => {
|
||||
// Click a specified primitive
|
||||
const [type, name] = id.split('_');
|
||||
const item = visibleCollection.find(i => i.itemLevel === 1 && i.type === type && i.name === name);
|
||||
setSelectedPrimitiveItem(item as PrimitiveSubItemTypes);
|
||||
setSelectedPrimitiveItem(item as PrimitiveSubItem);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{virtualItem => {
|
||||
const item = visibleCollection[virtualItem.index];
|
||||
const label = 'title' in item ? item.title : item.name;
|
||||
const uniqueId = item.itemLevel === 0 ? `root_${item.type}` : `${item.type}_${item.name}`;
|
||||
const itemLevel = item.itemLevel;
|
||||
return (
|
||||
<GridListItem
|
||||
id={uniqueId}
|
||||
className={`group absolute left-0 top-0 w-full select-none outline-none ${item.itemLevel === 0 ? 'data-[drop-target]:bg-[--hl-md]' : 'border-solid data-[drop-target]:border-b data-[drop-target]:border-[--color-surprise]'}`}
|
||||
textValue={label}
|
||||
data-testid={`test-${uniqueId}`}
|
||||
<CollectionGridListItem
|
||||
activeRequest={activeRequest}
|
||||
item={item}
|
||||
collapsedPrimitives={collapsedPrimitives}
|
||||
style={{
|
||||
height: `${virtualItem.size}`,
|
||||
height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden pl-4 pr-2 text-[--hl] outline-none transition-colors group-hover:bg-[--hl-xs] group-focus:bg-[--hl-sm] data-[selected=true]:text-[--color-font]"
|
||||
style={{
|
||||
paddingLeft: `${itemLevel}em`,
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden px-4 text-[--hl] outline-none transition-colors">
|
||||
{itemLevel === 0 && (
|
||||
<Icon
|
||||
className="w-4 flex-shrink-0"
|
||||
icon={collapsedPrimitives.includes(item.type) ? 'caret-right' : 'caret-down'}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'tools' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-success-rgb),0.5)] text-[0.65rem] text-[--color-font-success]">
|
||||
Tool
|
||||
</span>
|
||||
)}
|
||||
{item.type === 'resources' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-surprise-rgb),0.5)] text-[0.65rem] text-[--color-font-surprise]">
|
||||
Res
|
||||
</span>
|
||||
)}
|
||||
{item.type === 'prompts' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-info-rgb),0.5)] text-[0.65rem] text-[--color-font-info]">
|
||||
Prompt
|
||||
</span>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
</GridListItem>
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</GridList>
|
||||
@@ -438,7 +416,9 @@ const McpPage = () => {
|
||||
<PanelGroup autoSaveId="insomnia-panels" id="insomnia-panels" direction={direction}>
|
||||
<Panel id="mcp-request-pane" order={1} minSize={10} className="pane-one theme--pane">
|
||||
<McpRequestPane
|
||||
selectedPrimitiveItem={selectedPrimitiveItem}
|
||||
selectedPrimitiveItem={
|
||||
selectedPrimitiveItem?.itemLevel === 1 ? (selectedPrimitiveItem as PrimitiveSubItem) : null
|
||||
}
|
||||
environment={activeEnvironment}
|
||||
readyState={readyState}
|
||||
/>
|
||||
@@ -454,4 +434,76 @@ const McpPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionGridListItem = ({
|
||||
activeRequest,
|
||||
style,
|
||||
item,
|
||||
collapsedPrimitives,
|
||||
}: {
|
||||
activeRequest: McpRequest;
|
||||
item: PrimitiveTypeItem | PrimitiveSubItem;
|
||||
style: React.CSSProperties;
|
||||
collapsedPrimitives: McpServerPrimitiveTypes[];
|
||||
}) => {
|
||||
const label = 'title' in item ? item.title : item.name;
|
||||
const uniqueId = item.itemLevel === 0 ? `root_${item.type}` : `${item.type}_${item.name}`;
|
||||
const itemLevel = item.itemLevel;
|
||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<GridListItem
|
||||
id={uniqueId}
|
||||
className={`group absolute left-0 top-0 w-full select-none outline-none ${item.itemLevel === 0 ? 'data-[drop-target]:bg-[--hl-md]' : 'border-solid data-[drop-target]:border-b data-[drop-target]:border-[--color-surprise]'}`}
|
||||
textValue={label}
|
||||
data-testid={`test-${uniqueId}`}
|
||||
style={style}
|
||||
ref={triggerRef}
|
||||
>
|
||||
<div
|
||||
onContextMenu={e => {
|
||||
e.preventDefault();
|
||||
setIsContextMenuOpen(true);
|
||||
}}
|
||||
className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden pl-4 pr-2 text-[--hl] outline-none transition-colors group-hover:bg-[--hl-xs] group-focus:bg-[--hl-sm] data-[selected=true]:text-[--color-font]"
|
||||
style={{
|
||||
paddingLeft: `${itemLevel}em`,
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-[--line-height-xs] w-full select-none items-center gap-2 overflow-hidden px-4 text-[--hl] outline-none transition-colors">
|
||||
{itemLevel === 0 && (
|
||||
<Icon
|
||||
className="w-4 flex-shrink-0"
|
||||
icon={collapsedPrimitives.includes(item.type) ? 'caret-right' : 'caret-down'}
|
||||
/>
|
||||
)}
|
||||
{item.type === 'tools' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-success-rgb),0.5)] text-[0.65rem] text-[--color-font-success]">
|
||||
Tool
|
||||
</span>
|
||||
)}
|
||||
{item.type === 'resources' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-surprise-rgb),0.5)] text-[0.65rem] text-[--color-font-surprise]">
|
||||
Res
|
||||
</span>
|
||||
)}
|
||||
{item.type === 'prompts' && item.itemLevel === 1 && (
|
||||
<span className="flex w-10 flex-shrink-0 items-center justify-center rounded-sm border border-solid border-[--hl-sm] bg-[rgba(var(--color-info-rgb),0.5)] text-[0.65rem] text-[--color-font-info]">
|
||||
Prompt
|
||||
</span>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
<McpActionsDropdown
|
||||
item={item}
|
||||
request={activeRequest}
|
||||
isOpen={isContextMenuOpen}
|
||||
onOpenChange={setIsContextMenuOpen}
|
||||
triggerRef={triggerRef}
|
||||
/>
|
||||
</div>
|
||||
</GridListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default McpPage;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import React from 'react';
|
||||
import { Button, Collection, Header, Menu, MenuItem, MenuSection, MenuTrigger, Popover } from 'react-aria-components';
|
||||
|
||||
import type { PlatformKeyCombinations } from '../../../common/settings';
|
||||
import type { McpRequest } from '../../../models/mcp-request';
|
||||
import { Icon } from '../icon';
|
||||
import type { PrimitiveSubItem, PrimitiveTypeItem } from '../mcp/types';
|
||||
|
||||
interface Props {
|
||||
item: PrimitiveTypeItem | PrimitiveSubItem;
|
||||
request: McpRequest;
|
||||
triggerRef: React.RefObject<HTMLDivElement>;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const McpActionsDropdown = ({ item, request, isOpen, onOpenChange, triggerRef }: Props) => {
|
||||
const { itemLevel, type } = item;
|
||||
|
||||
if (itemLevel !== 0) {
|
||||
// Only show for capability type item
|
||||
return null;
|
||||
}
|
||||
|
||||
const requestId = request._id;
|
||||
|
||||
const handleRefreshPrimitive = () => {
|
||||
if (type === 'tools') {
|
||||
window.main.mcp.primitive.listTools({ requestId });
|
||||
} else if (type === 'prompts') {
|
||||
window.main.mcp.primitive.listPrompts({ requestId });
|
||||
} else if (type === 'resources') {
|
||||
window.main.mcp.primitive.listResources({ requestId });
|
||||
}
|
||||
};
|
||||
|
||||
const mcpPrimitiveActionList: {
|
||||
name: string;
|
||||
id: string;
|
||||
icon: IconName;
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
name: 'Actions',
|
||||
id: 'actions',
|
||||
icon: 'cog',
|
||||
items: [
|
||||
{
|
||||
id: 'Refresh',
|
||||
name: 'Refresh',
|
||||
action: handleRefreshPrimitive,
|
||||
icon: 'refresh',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<MenuTrigger
|
||||
isOpen={isOpen}
|
||||
onOpenChange={isOpen => {
|
||||
onOpenChange(isOpen);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid={`Dropdown-${item.type}`}
|
||||
aria-label="Mcp Actions"
|
||||
className="hidden aspect-square h-6 items-center justify-center rounded-sm text-sm text-[--color-font] ring-1 ring-transparent transition-all hover:bg-[--hl-xs] focus:ring-inset focus:ring-[--hl-md] group-hover:flex group-focus:flex aria-pressed:bg-[--hl-sm]"
|
||||
>
|
||||
<Icon icon="caret-down" />
|
||||
</Button>
|
||||
<Popover
|
||||
className="flex min-w-max flex-col overflow-y-hidden"
|
||||
triggerRef={triggerRef}
|
||||
placement="bottom end"
|
||||
offset={5}
|
||||
>
|
||||
<Menu
|
||||
aria-label="Mcp Actions Menu"
|
||||
selectionMode="single"
|
||||
onAction={key =>
|
||||
mcpPrimitiveActionList
|
||||
.find(i => i.items.find(a => a.id === key))
|
||||
?.items.find(a => a.id === key)
|
||||
?.action()
|
||||
}
|
||||
items={mcpPrimitiveActionList}
|
||||
className="min-w-max select-none overflow-y-auto rounded-md border border-solid border-[--hl-sm] bg-[--color-bg] py-2 text-sm shadow-lg focus:outline-none"
|
||||
>
|
||||
{section => (
|
||||
<MenuSection className="flex flex-1 flex-col">
|
||||
<Header className="flex items-center gap-2 py-1 pl-2 text-xs uppercase text-[--hl]">
|
||||
<Icon icon={section.icon} /> <span>{section.name}</span>
|
||||
</Header>
|
||||
<Collection items={section.items}>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="text-md flex h-[--line-height-xs] w-full items-center gap-2 whitespace-nowrap bg-transparent px-[--padding-md] text-[--color-font] transition-colors hover:bg-[--hl-sm] focus:bg-[--hl-xs] focus:outline-none disabled:cursor-not-allowed aria-selected:font-bold"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</MenuSection>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
);
|
||||
};
|
||||
@@ -8,17 +8,14 @@ import { useRequestResponseDeleteAllActionFetcher } from '~/routes/organization.
|
||||
|
||||
import { decompressObject } from '../../../common/misc';
|
||||
import * as models from '../../../models/index';
|
||||
import { isMcpResponse, type McpResponse } from '../../../models/mcp-response';
|
||||
import { isRequest, type Request } from '../../../models/request';
|
||||
import { type RequestVersion } from '../../../models/request-version';
|
||||
import type { Response } from '../../../models/response';
|
||||
import { isSocketIOResponse, type SocketIOResponse } from '../../../models/socket-io-response';
|
||||
import type { WebSocketRequest } from '../../../models/websocket-request';
|
||||
import { isWebSocketResponse, type WebSocketResponse } from '../../../models/websocket-response';
|
||||
import { useWorkspaceLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId';
|
||||
import {
|
||||
type RequestLoaderData,
|
||||
useRequestLoaderData,
|
||||
type WebSocketRequestLoaderData,
|
||||
} from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId';
|
||||
import { useRequestMetaPatcher } from '../../hooks/use-request';
|
||||
import { Dropdown, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
|
||||
@@ -28,10 +25,16 @@ import { TimeTag } from '../tags/time-tag';
|
||||
import { URLTag } from '../tags/url-tag';
|
||||
import { TimeFromNow } from '../time-from-now';
|
||||
|
||||
type ResponseType = Response | WebSocketResponse | SocketIOResponse | McpResponse;
|
||||
|
||||
export const ResponseHistoryDropdown = ({
|
||||
activeResponse,
|
||||
responses,
|
||||
requestVersions,
|
||||
}: {
|
||||
activeResponse: Response | WebSocketResponse | SocketIOResponse;
|
||||
activeResponse: ResponseType;
|
||||
responses: ResponseType[];
|
||||
requestVersions: RequestVersion[];
|
||||
}) => {
|
||||
const { organizationId, projectId, workspaceId, requestId } = useParams() as {
|
||||
organizationId: string;
|
||||
@@ -42,9 +45,9 @@ export const ResponseHistoryDropdown = ({
|
||||
const dropdownRef = useRef<DropdownHandle>(null);
|
||||
const patchRequestMeta = useRequestMetaPatcher();
|
||||
const { activeEnvironment } = useWorkspaceLoaderData()!;
|
||||
const { responses, requestVersions } = useRequestLoaderData() as RequestLoaderData | WebSocketRequestLoaderData;
|
||||
// const { responses, requestVersions } = useRequestLoaderData() as RequestLoaderData | WebSocketRequestLoaderData;
|
||||
const now = new Date();
|
||||
const categories: Record<string, (Response | WebSocketResponse)[]> = {
|
||||
const categories: Record<string, ResponseType[]> = {
|
||||
minutes: [],
|
||||
hours: [],
|
||||
today: [],
|
||||
@@ -56,7 +59,7 @@ export const ResponseHistoryDropdown = ({
|
||||
const deleteAllReponsesFetcher = useRequestResponseDeleteAllActionFetcher();
|
||||
|
||||
const handleSetActiveResponse = useCallback(
|
||||
async (requestId: string, activeResponse: Response | WebSocketResponse) => {
|
||||
async (requestId: string, activeResponse: ResponseType) => {
|
||||
if (isWebSocketResponse(activeResponse)) {
|
||||
window.main.webSocket.close({ requestId });
|
||||
}
|
||||
@@ -65,6 +68,10 @@ export const ResponseHistoryDropdown = ({
|
||||
window.main.socketIO.close({ requestId });
|
||||
}
|
||||
|
||||
if (isMcpResponse(activeResponse)) {
|
||||
window.main.mcp.close({ requestId });
|
||||
}
|
||||
|
||||
if (activeResponse.requestVersionId) {
|
||||
await models.requestVersion.restore(activeResponse.requestVersionId);
|
||||
}
|
||||
@@ -80,6 +87,8 @@ export const ResponseHistoryDropdown = ({
|
||||
window.main.webSocket.close({ requestId });
|
||||
} else if (isSocketIOResponse(activeResponse)) {
|
||||
window.main.socketIO.close({ requestId });
|
||||
} else if (isMcpResponse(activeResponse)) {
|
||||
window.main.socketIO.close({ requestId });
|
||||
}
|
||||
deleteResponsesSubmit({
|
||||
organizationId,
|
||||
@@ -96,12 +105,14 @@ export const ResponseHistoryDropdown = ({
|
||||
window.main.webSocket.close({ requestId });
|
||||
} else if (isSocketIOResponse(activeResponse)) {
|
||||
window.main.socketIO.close({ requestId });
|
||||
} else if (isMcpResponse(activeResponse)) {
|
||||
window.main.socketIO.close({ requestId });
|
||||
}
|
||||
}
|
||||
deleteResponseSubmit({ organizationId, projectId, workspaceId, requestId, responseId: activeResponse._id });
|
||||
}, [activeResponse, deleteResponseSubmit, organizationId, projectId, workspaceId, requestId]);
|
||||
|
||||
responses.forEach((response: Response | WebSocketResponse) => {
|
||||
responses.forEach(response => {
|
||||
const responseTime = new Date(response.created);
|
||||
const match =
|
||||
Object.entries({
|
||||
@@ -114,7 +125,7 @@ export const ResponseHistoryDropdown = ({
|
||||
categories[match].push(response);
|
||||
});
|
||||
|
||||
const renderResponseRow = (response: Response | WebSocketResponse) => {
|
||||
const renderResponseRow = (response: ResponseType) => {
|
||||
const activeResponseId = activeResponse ? activeResponse._id : 'n/a';
|
||||
const active = response._id === activeResponseId;
|
||||
const requestVersion = requestVersions.find(({ _id }) => _id === response.requestVersionId);
|
||||
@@ -145,7 +156,7 @@ export const ResponseHistoryDropdown = ({
|
||||
tooltipDelay={1000}
|
||||
/>
|
||||
<TimeTag milliseconds={response.elapsedTime} small tooltipDelay={1000} />
|
||||
{!isWebSocketResponse(response) && !isSocketIOResponse(response) && (
|
||||
{!isWebSocketResponse(response) && !isSocketIOResponse(response) && !isMcpResponse(response) && (
|
||||
<SizeTag
|
||||
bytesRead={response.bytesRead}
|
||||
bytesContent={response.bytesContent}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { IconDefinition, IconName, IconPrefix } from '@fortawesome/fontawesome-common-types';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons';
|
||||
import { far } from '@fortawesome/free-regular-svg-icons';
|
||||
@@ -6,8 +7,6 @@ import { FontAwesomeIcon, type FontAwesomeIconProps } from '@fortawesome/react-f
|
||||
|
||||
library.add(fas, far, fab);
|
||||
|
||||
import type { IconDefinition, IconName, IconPrefix } from '@fortawesome/fontawesome-common-types';
|
||||
|
||||
const customMcpIcon: IconDefinition = {
|
||||
prefix: 'fac' as IconPrefix, // custom prefix for "custom" icons, avoids conflicts with standard prefixes
|
||||
iconName: 'mcp' as IconName,
|
||||
|
||||
121
packages/insomnia/src/ui/components/mcp/event-view.tsx
Normal file
121
packages/insomnia/src/ui/components/mcp/event-view.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { Button } from 'react-aria-components';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import {
|
||||
getPreviewModeName,
|
||||
PREVIEW_MODE_FRIENDLY,
|
||||
PREVIEW_MODE_RAW,
|
||||
PREVIEW_MODE_SOURCE,
|
||||
PREVIEW_MODES,
|
||||
} from '../../../common/constants';
|
||||
import type { McpEvent } from '../../../main/network/mcp';
|
||||
import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
|
||||
import { CodeEditor } from '../../components/.client/codemirror/code-editor';
|
||||
import { showError } from '../../components/modals';
|
||||
import { useRequestMetaPatcher } from '../../hooks/use-request';
|
||||
import { Dropdown, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
|
||||
interface Props {
|
||||
event: McpEvent;
|
||||
}
|
||||
|
||||
export const MessageEventView = ({ event }: Props) => {
|
||||
const { requestId } = useParams() as { requestId: string };
|
||||
const raw = JSON.stringify('data' in event ? event.data : '');
|
||||
|
||||
const handleDownloadResponseBody = useCallback(async () => {
|
||||
const { canceled, filePath: outputPath } = await window.dialog.showSaveDialog({
|
||||
title: 'Save Response Body',
|
||||
buttonLabel: 'Save',
|
||||
});
|
||||
|
||||
if (canceled || !outputPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const to = fs.createWriteStream(outputPath);
|
||||
|
||||
to.on('error', err => {
|
||||
showError({
|
||||
title: 'Save Failed',
|
||||
message: 'Failed to save response body',
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
|
||||
to.write(raw);
|
||||
|
||||
to.end();
|
||||
}, [raw]);
|
||||
|
||||
const handleCopyResponseToClipboard = useCallback(() => {
|
||||
window.clipboard.writeText(raw);
|
||||
}, [raw]);
|
||||
|
||||
const patchRequestMeta = useRequestMetaPatcher();
|
||||
|
||||
let pretty = raw;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
pretty = JSON.stringify(parsed, null, '\t');
|
||||
} catch {
|
||||
// Can't parse as JSON.
|
||||
}
|
||||
const { activeRequestMeta } = useMcpRequestLoaderData()!;
|
||||
const previewMode = ('previewMode' in activeRequestMeta && activeRequestMeta.previewMode) || PREVIEW_MODE_SOURCE;
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="box-border flex h-8 flex-row border-b border-gray-300 p-2">
|
||||
<Dropdown
|
||||
aria-label="Websocket Preview Mode Dropdown"
|
||||
triggerButton={
|
||||
<Button className="tall">
|
||||
{getPreviewModeName(previewMode)}
|
||||
<i className="fa fa-caret-down space-left" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<DropdownSection aria-label="Preview Mode Section" title="Preview Mode">
|
||||
{PREVIEW_MODES.map(mode => (
|
||||
<DropdownItem aria-label={getPreviewModeName(mode, true)} key={mode}>
|
||||
<ItemContent
|
||||
icon={previewMode === mode ? 'check' : 'empty'}
|
||||
label={getPreviewModeName(mode, true)}
|
||||
onClick={() => patchRequestMeta(requestId, { previewMode: mode })}
|
||||
/>
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownSection>
|
||||
<DropdownSection aria-label="Actions Section" title="Actions">
|
||||
<DropdownItem aria-label="Copy raw response">
|
||||
<ItemContent icon="copy" label="Copy raw response" onClick={handleCopyResponseToClipboard} />
|
||||
</DropdownItem>
|
||||
<DropdownItem aria-label="Export raw response">
|
||||
<ItemContent icon="save" label="Export raw response" onClick={handleDownloadResponseBody} />
|
||||
</DropdownItem>
|
||||
</DropdownSection>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className="flex-grow p-4">
|
||||
<CodeEditor
|
||||
id="mcp-data-preview"
|
||||
hideLineNumbers
|
||||
mode={previewMode === PREVIEW_MODE_RAW ? 'text/plain' : 'text/json'}
|
||||
defaultValue={previewMode === PREVIEW_MODE_FRIENDLY ? pretty : raw}
|
||||
uniquenessKey={event._id}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const McpEventView = ({ event }: Props) => {
|
||||
if (event.type === 'message') {
|
||||
return <MessageEventView event={event} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -5,7 +5,6 @@ import React, { type FC, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Heading, Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import type { PrimitiveSubItemTypes } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp';
|
||||
import { InsomniaRjsfForm } from '~/ui/components/rjsf';
|
||||
|
||||
import { type AuthTypes } from '../../../common/constants';
|
||||
@@ -18,6 +17,7 @@ import { AuthWrapper } from '../editors/auth/auth-wrapper';
|
||||
import { readOnlyWebsocketPairs, RequestHeadersEditor } from '../editors/request-headers-editor';
|
||||
import { Pane } from '../panes/pane';
|
||||
import { McpUrlActionBar } from './mcp-url-bar';
|
||||
import type { PrimitiveSubItem } from './types';
|
||||
|
||||
const supportedAuthTypes: AuthTypes[] = ['apikey', 'basic', 'bearer'];
|
||||
|
||||
@@ -47,7 +47,7 @@ const PaneReadOnlyBanner = () => {
|
||||
interface Props {
|
||||
environment: Environment | null;
|
||||
readyState: boolean;
|
||||
selectedPrimitiveItem?: PrimitiveSubItemTypes | null;
|
||||
selectedPrimitiveItem?: PrimitiveSubItem | null;
|
||||
}
|
||||
|
||||
export const McpRequestPane: FC<Props> = ({ environment, readyState, selectedPrimitiveItem }) => {
|
||||
|
||||
18
packages/insomnia/src/ui/components/mcp/types.ts
Normal file
18
packages/insomnia/src/ui/components/mcp/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Prompt, Resource, ResourceTemplate, Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
import type { McpServerPrimitiveTypes } from '../../../models/mcp-request';
|
||||
|
||||
interface CommonItemProps {
|
||||
itemLevel: number;
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
export type ToolItem = Tool & { type: 'tools' } & CommonItemProps;
|
||||
export type ResourceItem = Resource & { type: 'resources' } & CommonItemProps;
|
||||
export type ResourceTemplateItem = ResourceTemplate & { type: 'resources' } & CommonItemProps;
|
||||
export type PromptItem = Prompt & { type: 'prompts' } & CommonItemProps;
|
||||
export type PrimitiveSubItem = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
|
||||
export interface PrimitiveTypeItem extends CommonItemProps {
|
||||
type: McpServerPrimitiveTypes;
|
||||
name: string;
|
||||
}
|
||||
@@ -39,7 +39,8 @@ interface Props {
|
||||
activeRequestId: string;
|
||||
}
|
||||
export const ResponsePane: FC<Props> = ({ activeRequestId }) => {
|
||||
const { activeRequest, activeRequestMeta, activeResponse } = useRequestLoaderData() as RequestLoaderData;
|
||||
const { activeRequest, activeRequestMeta, activeResponse, responses, requestVersions } =
|
||||
useRequestLoaderData() as RequestLoaderData;
|
||||
const filterHistory = activeRequestMeta.responseFilterHistory || [];
|
||||
const filter = activeRequestMeta.responseFilter || '';
|
||||
const patchRequestMeta = useRequestMetaPatcher();
|
||||
@@ -161,7 +162,11 @@ export const ResponsePane: FC<Props> = ({ activeRequestId }) => {
|
||||
<TimeTag milliseconds={activeResponse.elapsedTime} steps={steps} />
|
||||
<SizeTag bytesRead={activeResponse.bytesRead} bytesContent={activeResponse.bytesContent} />
|
||||
</div>
|
||||
<ResponseHistoryDropdown activeResponse={activeResponse} />
|
||||
<ResponseHistoryDropdown
|
||||
activeResponse={activeResponse}
|
||||
responses={responses}
|
||||
requestVersions={requestVersions}
|
||||
/>
|
||||
</PaneHeader>
|
||||
)}
|
||||
<Tabs aria-label="Request group tabs" className="flex h-full w-full flex-1 flex-col">
|
||||
|
||||
@@ -4,26 +4,30 @@ import React, { type FC, useEffect, useRef } from 'react';
|
||||
import { Cell, Column, Row, Table, TableBody, TableHeader } from 'react-aria-components';
|
||||
|
||||
import type { CurlEvent } from '../../../main/network/curl';
|
||||
import type { McpEvent } from '../../../main/network/mcp';
|
||||
import type { SocketIOEvent } from '../../../main/network/socket-io';
|
||||
import type { WebSocketEvent } from '../../../main/network/websocket';
|
||||
import { type IconId, SvgIcon } from '../svg-icon';
|
||||
|
||||
type EventTypes = WebSocketEvent | CurlEvent | SocketIOEvent | McpEvent;
|
||||
const Timestamp: FC<{ time: Date | number }> = ({ time }) => {
|
||||
const date = format(time, 'HH:mm:ss');
|
||||
return <>{date}</>;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
events: (WebSocketEvent | CurlEvent | SocketIOEvent)[];
|
||||
events: EventTypes[];
|
||||
selectionId?: string;
|
||||
onSelect: (event: WebSocketEvent | CurlEvent | SocketIOEvent) => void;
|
||||
onSelect: (event: EventTypes) => void;
|
||||
}
|
||||
|
||||
const isSocketIOEvent = (event: WebSocketEvent | CurlEvent | SocketIOEvent): event is SocketIOEvent => {
|
||||
const isSocketIOEvent = (event: EventTypes): event is SocketIOEvent => {
|
||||
return 'eventName' in event && typeof event.eventName === 'string';
|
||||
};
|
||||
|
||||
function getIcon(event: WebSocketEvent | CurlEvent | SocketIOEvent): IconId {
|
||||
const isMcpEvent = (event: EventTypes): event is McpEvent => event._id.toString().startsWith('mcp-');
|
||||
|
||||
function getIcon(event: EventTypes): IconId {
|
||||
switch (event.type) {
|
||||
case 'message': {
|
||||
if (event.direction === 'OUTGOING') {
|
||||
@@ -55,7 +59,7 @@ function getIcon(event: WebSocketEvent | CurlEvent | SocketIOEvent): IconId {
|
||||
}
|
||||
}
|
||||
|
||||
const getMessage = (event: WebSocketEvent | CurlEvent | SocketIOEvent): string | JSX.Element => {
|
||||
const getMessage = (event: EventTypes): string | JSX.Element => {
|
||||
switch (event.type) {
|
||||
case 'message': {
|
||||
if (isSocketIOEvent(event)) {
|
||||
@@ -71,6 +75,11 @@ const getMessage = (event: WebSocketEvent | CurlEvent | SocketIOEvent): string |
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isMcpEvent(event)) {
|
||||
if ('method' in event) {
|
||||
return <pre className="whitespace-pre-wrap">{event.method}</pre>;
|
||||
}
|
||||
}
|
||||
if ('data' in event && typeof event.data === 'object') {
|
||||
return 'Binary data';
|
||||
}
|
||||
|
||||
@@ -4,22 +4,26 @@ import React, { type FC, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Input, SearchField, Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import { useMcpRequestLoaderData } from '../../..//routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
|
||||
import { getSetCookieHeaders } from '../../../common/misc';
|
||||
import type { CurlEvent } from '../../../main/network/curl';
|
||||
import type { ResponseTimelineEntry } from '../../../main/network/libcurl-promise';
|
||||
import type { McpEvent } from '../../../main/network/mcp';
|
||||
import type { SocketIOEvent } from '../../../main/network/socket-io';
|
||||
import type { WebSocketEvent } from '../../../main/network/websocket';
|
||||
import { isMcpResponse, type McpResponse } from '../../../models/mcp-response';
|
||||
import type { RequestVersion } from '../../../models/request-version';
|
||||
import type { Response } from '../../../models/response';
|
||||
import { isSocketIOResponse, type SocketIOResponse } from '../../../models/socket-io-response';
|
||||
import type { WebSocketResponse } from '../../../models/websocket-response';
|
||||
import { type WebSocketResponse } from '../../../models/websocket-response';
|
||||
import { useRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId';
|
||||
import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
|
||||
import { deserializeNDJSON } from '../../../utils/ndjson';
|
||||
import { useReadyState } from '../../hooks/use-ready-state';
|
||||
import { useRealtimeConnectionEvents } from '../../hooks/use-realtime-connection-events';
|
||||
import { ResponseHistoryDropdown } from '../dropdowns/response-history-dropdown';
|
||||
import { ErrorBoundary } from '../error-boundary';
|
||||
import { Icon } from '../icon';
|
||||
import { McpEventView } from '../mcp/event-view';
|
||||
import { Pane, PaneHeader } from '../panes/pane';
|
||||
import { PlaceholderResponsePane } from '../panes/placeholder-response-pane';
|
||||
import { SocketIOEventView } from '../socket-io/event-view';
|
||||
@@ -35,7 +39,7 @@ import { EventLogView } from './event-log-view';
|
||||
import { EventView } from './event-view';
|
||||
|
||||
export const RealtimeResponsePane: FC<{ requestId: string }> = () => {
|
||||
const { activeResponse } = useRequestLoaderData()!;
|
||||
const { activeResponse, responses, requestVersions } = useRequestLoaderData()!;
|
||||
|
||||
if (!activeResponse) {
|
||||
return (
|
||||
@@ -45,11 +49,13 @@ export const RealtimeResponsePane: FC<{ requestId: string }> = () => {
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
return <RealtimeActiveResponsePane response={activeResponse} />;
|
||||
return (
|
||||
<RealtimeActiveResponsePane response={activeResponse} responses={responses} requestVersions={requestVersions} />
|
||||
);
|
||||
};
|
||||
|
||||
export const McpRealtimeResponsePane = () => {
|
||||
const { activeResponse } = useMcpRequestLoaderData()!;
|
||||
const { activeResponse, responses, requestVersions } = useMcpRequestLoaderData()!;
|
||||
|
||||
if (!activeResponse) {
|
||||
return (
|
||||
@@ -59,13 +65,19 @@ export const McpRealtimeResponsePane = () => {
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
return <RealtimeActiveResponsePane response={activeResponse} />;
|
||||
return (
|
||||
<RealtimeActiveResponsePane response={activeResponse} responses={responses} requestVersions={requestVersions} />
|
||||
);
|
||||
};
|
||||
|
||||
type ResponseType = WebSocketResponse | Response | SocketIOResponse | McpResponse;
|
||||
type EventType = CurlEvent | WebSocketEvent | SocketIOEvent | McpEvent;
|
||||
const RealtimeActiveResponsePane: FC<{
|
||||
response: WebSocketResponse | Response | SocketIOResponse;
|
||||
}> = ({ response }) => {
|
||||
const [selectedEvent, setSelectedEvent] = useState<CurlEvent | WebSocketEvent | SocketIOEvent | null>(null);
|
||||
response: ResponseType;
|
||||
responses: ResponseType[];
|
||||
requestVersions: RequestVersion[];
|
||||
}> = ({ response, responses, requestVersions }) => {
|
||||
const [selectedEvent, setSelectedEvent] = useState<EventType | null>(null);
|
||||
const [timeline, setTimeline] = useState<ResponseTimelineEntry[]>([]);
|
||||
const [clearEventsBefore, setClearEventsBefore] = useState<number | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -75,20 +87,26 @@ const RealtimeActiveResponsePane: FC<{
|
||||
if (isSocketIOResponse(response)) {
|
||||
return 'socketIO';
|
||||
}
|
||||
if (isMcpResponse(response)) {
|
||||
return 'mcp';
|
||||
}
|
||||
return response.type === 'WebSocketResponse' ? 'webSocket' : 'curl';
|
||||
}, [response]);
|
||||
|
||||
const allEvents = useRealtimeConnectionEvents({ responseId: response._id, protocol }) as (
|
||||
| CurlEvent
|
||||
| WebSocketEvent
|
||||
| SocketIOEvent
|
||||
)[];
|
||||
const allEvents = useRealtimeConnectionEvents({ responseId: response._id, protocol }) as EventType[];
|
||||
const requestId = response.parentId;
|
||||
const readyState = useReadyState({ requestId: requestId, protocol });
|
||||
const handleSelection = (event: CurlEvent | WebSocketEvent | SocketIOEvent) => {
|
||||
setSelectedEvent((selected: CurlEvent | WebSocketEvent | SocketIOEvent | null) =>
|
||||
selected?._id === event._id ? null : event,
|
||||
);
|
||||
const handleSelection = (event: EventType) => {
|
||||
setSelectedEvent((selected: EventType | null) => (selected?._id === event._id ? null : event));
|
||||
};
|
||||
const getEventView = (selectedEvent: EventType) => {
|
||||
if (isSocketIOResponse(response)) {
|
||||
return <SocketIOEventView event={selectedEvent as SocketIOEvent} key={selectedEvent._id} />;
|
||||
} else if (isMcpResponse(response)) {
|
||||
return <McpEventView event={selectedEvent as McpEvent} key={selectedEvent._id} />;
|
||||
}
|
||||
|
||||
return <EventView event={selectedEvent as WebSocketEvent} key={selectedEvent._id} />;
|
||||
};
|
||||
|
||||
const events = useMemo(
|
||||
@@ -157,7 +175,8 @@ const RealtimeActiveResponsePane: FC<{
|
||||
};
|
||||
}, [response.timelinePath, events.length]);
|
||||
|
||||
const cookieHeaders = !isSocketIOResponse(response) ? getSetCookieHeaders(response.headers) : [];
|
||||
const cookieHeaders =
|
||||
!isSocketIOResponse(response) && !isMcpResponse(response) ? getSetCookieHeaders(response.headers) : [];
|
||||
return (
|
||||
<Pane type="response">
|
||||
<PaneHeader className="row-spaced">
|
||||
@@ -174,7 +193,7 @@ const RealtimeActiveResponsePane: FC<{
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ResponseHistoryDropdown activeResponse={response} />
|
||||
<ResponseHistoryDropdown activeResponse={response} requestVersions={requestVersions} responses={responses} />
|
||||
</PaneHeader>
|
||||
<Tabs aria-label="Request group tabs" className="flex h-full w-full flex-1 flex-col">
|
||||
<TabList
|
||||
@@ -284,13 +303,7 @@ const RealtimeActiveResponsePane: FC<{
|
||||
<>
|
||||
<PanelResizeHandle className={'h-[1px] w-full bg-[--hl-md]'} />
|
||||
<Panel minSize={10} defaultSize={50}>
|
||||
<div className="h-full flex-1 border-t border-[var(--hl-md)]">
|
||||
{isSocketIOResponse(response) ? (
|
||||
<SocketIOEventView key={selectedEvent._id} event={selectedEvent as SocketIOEvent} />
|
||||
) : (
|
||||
<EventView key={selectedEvent._id} event={selectedEvent} />
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full flex-1 border-t border-[var(--hl-md)]">{getEventView(selectedEvent)}</div>
|
||||
</Panel>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { CurlEvent } from '../../main/network/curl';
|
||||
import type { McpEvent } from '../../main/network/mcp';
|
||||
import type { SocketIOEvent } from '../../main/network/socket-io';
|
||||
import type { WebSocketEvent } from '../../main/network/websocket';
|
||||
|
||||
@@ -11,7 +12,7 @@ export function useRealtimeConnectionEvents({
|
||||
responseId: string;
|
||||
protocol: 'curl' | 'webSocket' | 'socketIO' | 'mcp';
|
||||
}) {
|
||||
const [events, setEvents] = useState<CurlEvent[] | WebSocketEvent[] | SocketIOEvent[]>([]);
|
||||
const [events, setEvents] = useState<CurlEvent[] | WebSocketEvent[] | SocketIOEvent[] | McpEvent[]>([]);
|
||||
const updateEvents = useCallback(async () => {
|
||||
const allEvents = await window.main[protocol].event.findMany({ responseId });
|
||||
setEvents(allEvents);
|
||||
|
||||
Reference in New Issue
Block a user