diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 068ca3e4a..859e509c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -174,15 +174,24 @@ rsvg-convert -w 128 -h 128 images/dh-logo-128.svg -o images/dh-logo-128.png ``` ## Icon Font Generation -The extension uses an icon font generated from SVGs in `@deephaven/icons`. Running the generator requires a local checkout of web-client-ui. +The extension uses icon fonts generated from SVGs. There are two icon fonts: +1. `dh-ext`: Extension-specific icons from `icons/src` +2. `dh`: Deephaven icons from `@deephaven/icons` -The generator can be run via the following script, where `` is the location of `packages/icons/src/icons` in `web-client-ui`. +### Extension Icons +To generate the `dh-ext` icon font from local SVGs in `icons/src`: ```sh -npm run icon:gen -- +npm run icon:gen:ext ``` -The script will automatically copy `icons/dist/dh-icons.woff2` file to the `/assets` folder of the extension, but the contents of `icons/dist/dh/dh-contributes-icons.json` has to be manually copied to the `package.json` `contributes/icons` section. -> Note: All of the icons should be consumed via the `dh-xxx` icon ids, so no code changes should be necessary unless icons have been renamed or removed. +### Deephaven Icons +Generating the `dh` icon font requires a local checkout of web-client-ui. Run the following where `` is the location of `packages/icons/src/icons` in `web-client-ui`: +```sh +npm run icon:gen:dh -- +``` + +The script will automatically copy the `.woff2` files to the `/assets` folder of the extension, but the contents of `icons/dist/dh*/dh*-contributes-icons.json` files need to be manually copied to the `package.json` `contributes/icons` section. +> Note: All of the icons should be consumed via the `dh-xxx` or `dh-ext-xxx` icon ids, so no code changes should be necessary unless icons have been renamed or removed. ## Implementation Notes diff --git a/assets/dh-ext-icons.woff2 b/assets/dh-ext-icons.woff2 new file mode 100644 index 000000000..404c4eb73 Binary files /dev/null and b/assets/dh-ext-icons.woff2 differ diff --git a/docs/assets/copilot-mcp-servers.png b/docs/assets/copilot-mcp-servers.png new file mode 100644 index 000000000..98cfdc438 Binary files /dev/null and b/docs/assets/copilot-mcp-servers.png differ diff --git a/docs/assets/copilot-mcp-settings.png b/docs/assets/copilot-mcp-settings.png new file mode 100644 index 000000000..a12e12883 Binary files /dev/null and b/docs/assets/copilot-mcp-settings.png differ diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 000000000..72be5c4ec --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,54 @@ +# Deephaven VS Code MCP Server (experimental) + +## MCP Tools + +The extension provides 17 MCP tools for interacting with Deephaven servers: + +### Server & Connection Management +- **connectToServer** - Create a connection to a Deephaven server +- **listConnections** - List all active Deephaven connections +- **listServers** - List all Deephaven servers with optional filtering +- **setEditorConnection** - Set connection for an editor by URI +- **startPipServer** - Start a managed Deephaven pip server + +### Code Execution +- **runCode** - Execute arbitrary code text in a Deephaven session +- **runCodeFromUri** - Execute code from a workspace file URI + +### Table Operations +- **getColumnStats** - Get statistical information for a table column +- **getTableStats** - Get schema and statistics for a table +- **queryTableData** - Query table data with filters, aggregations, and sorting + +### Panel & Variable Management +- **listPanelVariables** - List panel variables for a connection +- **openVariablePanels** - Open variable panels for a connection + +### Workspace & File Management +- **addRemoteFileSources** - Add remote file source folders to the workspace +- **openFilesInEditor** - Open files in VS Code editor + +### Diagnostics & Utilities +- **checkPythonEnvironment** - Check if Python environment supports Deephaven pip server +- **getLogs** - Get log history from debug output channel +- **showOutputPanel** - Show Deephaven output panel in VS Code + +## Installation + +For VS Code and Windsurf, you can manually download and install the latest mcp-dev .vsix: + + [vscode-deephaven-1.1.10-mcp-dev.0.vsix](https://github.com/deephaven/vscode-deephaven/raw/refs/heads/mcp/releases/vscode-deephaven-1.1.10-mcp-dev.0.vsix) + +### VS Code +- Download and install the .vsix +- Restart VS Code +- A toast message should say that Deephaven MCP Server has started +- No need to configure anything. Copilot should now have access to the MCP server + +### Windsurf +- Download and install the .vsix +- Restart Windsurf +- Should see a prompt "Your Windsurf MCP config doesn't match this workspace's 'Deephaven VS Code MCP Server'. Update to port XXXXX?" +- Say "yes" for this time or "always" for it to automatically update when you switch windows + +> Note Windsurf only supports a global mcp server configuration (`~/.codeium/windsurf/mcp_config.json`), but the extension assigns a unique MCP port to each workspace. This means whenever you switch workspaces, you will be prompted to sync the port again so that Cascade can talk to the correct MCP server. \ No newline at end of file diff --git a/icons/generate.mjs b/icons/generate.mjs index 0a5952a52..0e01dc6b9 100644 --- a/icons/generate.mjs +++ b/icons/generate.mjs @@ -1,8 +1,12 @@ /** * This script generates an icon font + manifest from a directory of SVG files. + * * Usage: + * npm run icon:gen:ext # Generate dh-ext icons from icons/src + * npm run icon:gen:dh -- # Generate dh icons from @deephaven/icons * - * npm run icon:gen -- + * If running the script directly, both prefix and path are required: + * node icons/generate.mjs */ /* eslint-disable no-console */ diff --git a/icons/src/logo.svg b/icons/src/logo.svg new file mode 100644 index 000000000..c8afb947b --- /dev/null +++ b/icons/src/logo.svg @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9372fd6aa..ed469a68f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,14 @@ "@deephaven-enterprise/auth-nodejs": "^1.20250219.134-beta", "@deephaven-enterprise/query-utils": "^1.20250219.134-beta", "@deephaven/jsapi-nodejs": "^1.3.0", + "@modelcontextprotocol/sdk": "^1.21.1", "archiver": "^7.0.1", "chai": "^4.5.0", - "nanoid": "^5.0.7" + "nanoid": "^5.0.7", + "zod": "^3.25.76" }, "devDependencies": { + "@cfworker/json-schema": "^4.1.1", "@deephaven-enterprise/jsapi-types": "^1.20250219.134-beta", "@deephaven/jsapi-types": "^1.0.0-dev0.37.3", "@types/archiver": "^6.0.3", @@ -335,6 +338,13 @@ "node": ">=18" } }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1095,6 +1105,18 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@hono/node-server": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1254,6 +1276,67 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", @@ -2712,6 +2795,53 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -2795,6 +2925,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3399,6 +3568,46 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3520,6 +3729,15 @@ "node": ">= 6" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", @@ -3753,7 +3971,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -3766,7 +3983,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -4201,17 +4417,70 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -4535,10 +4804,10 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -4679,6 +4948,15 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -4794,7 +5072,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -4827,11 +5104,26 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -4956,7 +5248,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -4965,7 +5256,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -4980,7 +5270,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -5146,6 +5435,12 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5535,6 +5830,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -5564,6 +5868,27 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -5639,6 +5964,89 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "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==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -5742,8 +6150,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-fifo": { "version": "1.3.2", @@ -5790,6 +6197,22 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -5838,6 +6261,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5930,6 +6374,24 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5998,7 +6460,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6099,7 +6560,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -6123,7 +6583,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -6376,7 +6835,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6468,7 +6926,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6501,7 +6958,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6519,6 +6975,16 @@ "he": "bin/he" } }, + "node_modules/hono": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", + "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -6595,6 +7061,26 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -6786,6 +7272,15 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -7395,6 +7890,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -7470,6 +7974,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8079,7 +8589,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -8090,6 +8599,15 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memoizee": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", @@ -8118,6 +8636,18 @@ "node": ">= 0.10.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8491,8 +9021,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -9006,11 +9535,19 @@ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -9107,11 +9644,22 @@ "https://opencollective.com/debug" ] }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -9289,6 +9837,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9337,6 +9894,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9396,6 +9963,15 @@ "node": ">=0.10" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -9534,6 +10110,19 @@ "node": ">=10" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9567,7 +10156,6 @@ "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -9626,6 +10214,46 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -9761,6 +10389,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -9934,6 +10571,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -10035,8 +10694,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-filename": { "version": "1.6.3", @@ -10102,6 +10760,53 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -10112,6 +10817,21 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -10169,6 +10889,12 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -10213,7 +10939,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -10232,7 +10957,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -10248,7 +10972,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -10266,7 +10989,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -10479,6 +11201,15 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -11202,7 +11933,16 @@ "is-number": "^7.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" } }, "node_modules/tough-cookie": { @@ -11423,6 +12163,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -11627,6 +12406,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unrs-resolver": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", @@ -11747,6 +12535,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", @@ -12706,8 +13503,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.18.0", @@ -12975,6 +13771,24 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, "packages/enterprise-jsapi-types": { "extraneous": true }, @@ -13230,6 +14044,12 @@ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true }, + "@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "devOptional": true + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -13645,6 +14465,12 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "@hono/node-server": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -13768,6 +14594,47 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "requires": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "@napi-rs/wasm-runtime": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", @@ -14741,6 +15608,35 @@ "event-target-shim": "^6.0.2" } }, + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "requires": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + } + } + }, "acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -14800,6 +15696,32 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -15222,6 +16144,32 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "requires": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -15319,6 +16267,11 @@ } } }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, "c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", @@ -15493,7 +16446,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "requires": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -15503,7 +16455,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -15820,17 +16771,46 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" + }, "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -16058,10 +17038,9 @@ } }, "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { "ms": "^2.1.3" } @@ -16155,6 +17134,11 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -16234,7 +17218,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -16264,11 +17247,21 @@ "safe-buffer": "^5.0.1" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -16377,14 +17370,12 @@ "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-module-lexer": { "version": "1.7.0", @@ -16396,7 +17387,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "requires": { "es-errors": "^1.3.0" } @@ -16524,6 +17514,11 @@ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -16814,6 +17809,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -16834,6 +17834,19 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, + "eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "requires": { + "eventsource-parser": "^3.0.1" + } + }, + "eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + }, "execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -16887,6 +17900,62 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true }, + "express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + } + } + }, + "express-rate-limit": { + "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==", + "requires": {} + }, "ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -16966,8 +18035,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-fifo": { "version": "1.3.2", @@ -17010,6 +18078,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" + }, "fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -17052,6 +18125,19 @@ "to-regex-range": "^5.0.1" } }, + "finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "requires": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -17117,6 +18203,16 @@ "mime-types": "^2.1.12" } }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -17170,8 +18266,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.8", @@ -17249,7 +18344,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -17267,7 +18361,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "requires": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -17448,8 +18541,7 @@ "gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "graceful-fs": { "version": "4.2.11", @@ -17508,8 +18600,7 @@ "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-tostringtag": { "version": "1.0.2", @@ -17530,7 +18621,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "requires": { "function-bind": "^1.1.2" } @@ -17542,6 +18632,12 @@ "dev": true, "peer": true }, + "hono": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", + "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "peer": true + }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -17601,6 +18697,18 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, + "http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "requires": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + } + }, "http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -17742,6 +18850,11 @@ "sprintf-js": "^1.1.3" } }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, "is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -18137,6 +19250,11 @@ "@isaacs/cliui": "^8.0.2" } }, + "jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==" + }, "js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -18198,6 +19316,11 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==" + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -18707,8 +19830,7 @@ "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, "mdurl": { "version": "1.0.1", @@ -18716,6 +19838,11 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, "memoizee": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", @@ -18738,6 +19865,11 @@ "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true }, + "merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -19023,8 +20155,7 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "mute-stream": { "version": "0.0.8", @@ -19414,11 +20545,15 @@ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "object-keys": { "version": "1.1.1", @@ -19481,11 +20616,18 @@ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -19616,6 +20758,11 @@ "parse5": "^7.0.0" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19649,6 +20796,11 @@ "minipass": "^7.1.2" } }, + "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==" + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -19690,6 +20842,11 @@ "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", "dev": true }, + "pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==" + }, "possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -19774,6 +20931,15 @@ "retry": "^0.12.0" } }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -19801,7 +20967,6 @@ "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "dev": true, "requires": { "side-channel": "^1.1.0" } @@ -19833,6 +20998,32 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "requires": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -19942,6 +21133,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -20068,6 +21264,25 @@ "fsevents": "~2.3.2" } }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "dependencies": { + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + } + } + }, "rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -20141,8 +21356,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-filename": { "version": "1.6.3", @@ -20186,6 +21400,39 @@ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true }, + "send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "requires": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + } + } + }, "serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -20196,6 +21443,17 @@ "randombytes": "^2.1.0" } }, + "serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "requires": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -20244,6 +21502,11 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -20276,7 +21539,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -20289,7 +21551,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -20299,7 +21560,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -20311,7 +21571,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -20460,6 +21719,11 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, "std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -21004,6 +22268,11 @@ "is-number": "^7.0.0" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -21157,6 +22426,31 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "requires": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "requires": { + "mime-db": "^1.54.0" + } + } + } + }, "typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -21303,6 +22597,11 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, "unrs-resolver": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", @@ -21413,6 +22712,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, "vite": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", @@ -22032,8 +23336,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { "version": "8.18.0", @@ -22220,6 +23523,17 @@ } } } + }, + "zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + }, + "zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "requires": {} } } } diff --git a/package.json b/package.json index 99c360c5f..d1e6fba86 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "docs:start": "./scripts/startDocs", "docs:format": "./scripts/formatDocs", "docs:validate": "./scripts/validateDocs", - "icon:gen": "node icons/generate.mjs dh", + "icon:gen:dh": "node icons/generate.mjs dh", + "icon:gen:ext": "node icons/generate.mjs dh-ext icons/src", "package:dev": "./scripts/package-dev.sh", "publish": "./scripts/publish.sh", "publish:prerelease": "./scripts/publish.sh --pre-release", @@ -149,6 +150,11 @@ ] }, "default": [] + }, + "deephaven.mcp.autoUpdateConfig": { + "type": "boolean", + "default": false, + "markdownDescription": "Automatically update MCP config without prompting when the extension's MCP server port changes. Applies to supported editors (e.g., Windsurf)." } } }, @@ -186,6 +192,10 @@ "command": "vscode-deephaven.clearSecretStorage", "title": "Deephaven: Clear Secrets" }, + { + "command": "vscode-deephaven.copyMcpUrl", + "title": "Deephaven: Copy MCP Server URL" + }, { "command": "vscode-deephaven.connectToServer", "title": "Connect to Server", @@ -710,8 +720,21 @@ "fontPath": "assets/dh-icons.woff2", "fontCharacter": "\\ee81" } + }, + "dh-ext-logo": { + "description": "Deephaven dh-ext-logo icon", + "default": { + "fontPath": "assets/dh-ext-icons.woff2", + "fontCharacter": "\\ee48" + } } }, + "mcpServerDefinitionProviders": [ + { + "id": "deephaven-vscode.mcpServer", + "label": "Deephaven VS Code MCP Server" + } + ], "menus": { "commandPalette": [ { @@ -1008,11 +1031,14 @@ "@deephaven-enterprise/auth-nodejs": "^1.20250219.134-beta", "@deephaven-enterprise/query-utils": "^1.20250219.134-beta", "@deephaven/jsapi-nodejs": "^1.3.0", + "@modelcontextprotocol/sdk": "^1.21.1", "archiver": "^7.0.1", "chai": "^4.5.0", - "nanoid": "^5.0.7" + "nanoid": "^5.0.7", + "zod": "^3.25.76" }, "devDependencies": { + "@cfworker/json-schema": "^4.1.1", "@deephaven-enterprise/jsapi-types": "^1.20250219.134-beta", "@deephaven/jsapi-types": "^1.0.0-dev0.37.3", "@types/archiver": "^6.0.3", diff --git a/releases/vscode-deephaven-1.1.10-mcp-dev.0.vsix b/releases/vscode-deephaven-1.1.10-mcp-dev.0.vsix new file mode 100644 index 000000000..60c89a42a Binary files /dev/null and b/releases/vscode-deephaven-1.1.10-mcp-dev.0.vsix differ diff --git a/releases/vscode-deephaven-1.1.6-mcp-dev.0.vsix b/releases/vscode-deephaven-1.1.6-mcp-dev.0.vsix new file mode 100644 index 000000000..3fa73ea49 Binary files /dev/null and b/releases/vscode-deephaven-1.1.6-mcp-dev.0.vsix differ diff --git a/releases/vscode-deephaven-1.1.6-mcp-dev.3.vsix b/releases/vscode-deephaven-1.1.6-mcp-dev.3.vsix new file mode 100644 index 000000000..0e2810db9 Binary files /dev/null and b/releases/vscode-deephaven-1.1.6-mcp-dev.3.vsix differ diff --git a/releases/vscode-deephaven-1.1.8-mcp-dev.0.vsix b/releases/vscode-deephaven-1.1.8-mcp-dev.0.vsix new file mode 100644 index 000000000..3e2603095 Binary files /dev/null and b/releases/vscode-deephaven-1.1.8-mcp-dev.0.vsix differ diff --git a/releases/vscode-deephaven-1.1.8-mcp-dev.1.vsix b/releases/vscode-deephaven-1.1.8-mcp-dev.1.vsix new file mode 100644 index 000000000..e89f05fc3 Binary files /dev/null and b/releases/vscode-deephaven-1.1.8-mcp-dev.1.vsix differ diff --git a/src/common/commands.ts b/src/common/commands.ts index bf699d93b..14e56573d 100644 --- a/src/common/commands.ts +++ b/src/common/commands.ts @@ -8,6 +8,7 @@ export type RunCodeCmdArgs = [ _arg?: { groupId: number }, constrainTo?: 'selection' | vscode.Range[], languageId?: string, + connectionUrl?: URL, ]; /** Arguments passed to `RUN_MARKDOWN_CODEBLOCK_CMD` handler */ @@ -70,3 +71,4 @@ export const START_SERVER_CMD = cmd('startServer'); export const STOP_SERVER_CMD = cmd('stopServer'); export const ADD_REMOTE_FILE_SOURCE_CMD = cmd('addRemoteFileSource'); export const REMOVE_REMOTE_FILE_SOURCE_CMD = cmd('removeRemoteFileSource'); +export const COPY_MCP_URL_CMD = cmd('copyMcpUrl'); diff --git a/src/common/constants.ts b/src/common/constants.ts index bed5721d9..cdb1d2bc0 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -16,6 +16,7 @@ export const CONFIG_KEY = { root: 'deephaven', coreServers: 'coreServers', enterpriseServers: 'enterpriseServers', + mcpAutoUpdateConfig: 'mcp.autoUpdateConfig', } as const; export const CENSORED_TEXT = '********' as const; @@ -172,6 +173,8 @@ export const AUTH_CONFIG_SAML_LOGIN_URL = 'authentication.client.samlauth.login.url' as const; export const CREATE_QUERY_SETTINGS_STORAGE_KEY = 'createQuerySettings' as const; +export const MCP_SERVER_NAME = 'Deephaven VS Code MCP Server' as const; +export const MCP_SERVER_PORT_STORAGE_KEY = 'deephaven.mcpServerPort' as const; export const DH_SAML_AUTH_PROVIDER_TYPE = 'dhsaml' as const; export const DH_SAML_SERVER_URL_SCOPE_KEY = 'deephaven.samlServerUrl' as const; diff --git a/src/controllers/ConnectionController.ts b/src/controllers/ConnectionController.ts index 605c8f00f..2f9fefa94 100644 --- a/src/controllers/ConnectionController.ts +++ b/src/controllers/ConnectionController.ts @@ -221,10 +221,12 @@ export class ConnectionController * Get or create a connection for the given uri. * @param uri Uri to get or create a connection for. * @param languageId Language id to use for the connection. + * @param serverOrWorkerUrl Optional server or worker URL that takes precedence for connection selection. */ getOrCreateConnection = async ( uri: vscode.Uri, - languageId: string + languageId: string, + serverOrWorkerUrl?: URL ): Promise => { assertDefined(this._outputChannel, 'outputChannel'); assertDefined(this._serverManager, 'serverManager'); @@ -234,11 +236,21 @@ export class ConnectionController let dhService = await this._serverManager.getEditorConnection(uri); if (dhService != null) { - return dhService; + if ( + serverOrWorkerUrl == null || + dhService.serverUrl.href === serverOrWorkerUrl.href + ) { + return dhService; + } + + dhService = null; } + // Get supporting connections and available servers, filtered by serverOrWorkerUrl if provided const supportingConnections = await getConnectionsForConsoleType( - this._serverManager.getConnections(), + serverOrWorkerUrl == null + ? this._serverManager.getConnections() + : this._serverManager.getConnections(serverOrWorkerUrl), languageId as ConsoleType ); @@ -247,7 +259,27 @@ export class ConnectionController hasConnections: false, }); - if (supportingConnections.length === 1 && availableServers.length === 0) { + if (serverOrWorkerUrl != null) { + // If serverOrWorkerUrl was specified, find and connect to that specific server + if (supportingConnections.length > 0) { + await this.connectEditor(supportingConnections[0], uri, languageId); + } else { + const server = availableServers.find( + s => s.url.href === serverOrWorkerUrl.href + ); + if (server == null) { + const logMsg = `Server not available: '${serverOrWorkerUrl.href}'.`; + logger.debug(logMsg); + this._outputChannel.appendLine(logMsg); + this._toaster.error(logMsg); + return null; + } + await this.connectEditor(server, uri, languageId); + } + } else if ( + supportingConnections.length === 1 && + availableServers.length === 0 + ) { // If we only have 1 supporting connection, and no available servers, use // the available connection. await this.connectEditor(supportingConnections[0], uri, languageId); @@ -285,7 +317,7 @@ export class ConnectionController * Handle connecting to a server */ onConnectToServer = async ( - serverState: ServerState, + serverState: Pick, operateAsAnotherUser?: boolean ): Promise => { const languageId = vscode.window.activeTextEditor?.document.languageId; @@ -295,7 +327,7 @@ export class ConnectionController const workerConsoleType = serverState.type === 'DHE' ? getConsoleType(languageId) : undefined; - this._serverManager?.connectToServer( + await this._serverManager?.connectToServer( serverState.url, workerConsoleType, operateAsAnotherUser diff --git a/src/controllers/ExtensionController.ts b/src/controllers/ExtensionController.ts index 35a9f7c80..357c4c613 100644 --- a/src/controllers/ExtensionController.ts +++ b/src/controllers/ExtensionController.ts @@ -10,13 +10,17 @@ import { type UnauthenticatedClient as DheUnauthenticatedClient, } from '@deephaven-enterprise/auth-nodejs'; import { NodeHttp2gRPCTransport } from '@deephaven/jsapi-nodejs'; +import { MCPServer } from '../mcp'; import { ADD_REMOTE_FILE_SOURCE_CMD, CLEAR_SECRET_STORAGE_CMD, + COPY_MCP_URL_CMD, CREATE_NEW_TEXT_DOC_CMD, DELETE_VARIABLE_CMD, DOWNLOAD_LOGS_CMD, GENERATE_REQUIREMENTS_TXT_CMD, + MCP_SERVER_NAME, + MCP_SERVER_PORT_STORAGE_KEY, OPEN_IN_BROWSER_CMD, REFRESH_PANELS_TREE_CMD, REFRESH_REMOTE_IMPORT_SOURCE_TREE_CMD, @@ -66,6 +70,7 @@ import { SamlAuthProvider, RunMarkdownCodeBlockHoverProvider, CreateQueryViewProvider, + McpServerDefinitionProvider, } from '../providers'; import { CoreJsApiCache, @@ -192,6 +197,10 @@ export class ExtensionController implements IDisposable { private _secretService: ISecretService | null = null; private _serverManager: IServerManager | null = null; private _userLoginController: UserLoginController | null = null; + private _mcpServer: MCPServer | null = null; + private _mcpServerDefinitionProvider: McpServerDefinitionProvider | null = + null; + private _mcpStatusBarItem: vscode.StatusBarItem | null = null; // Tree providers private _serverTreeProvider: ServerTreeProvider | null = null; @@ -240,7 +249,9 @@ export class ExtensionController implements IDisposable { this.initializePanelController(); this.initializePipServerController(); this.initializeUserLoginController(); + this.initializeStatusBar(); this.initializeCommands(); + this.initializeMCPServer(); this._context.subscriptions.push(NodeHttp2gRPCTransport); @@ -252,6 +263,7 @@ export class ExtensionController implements IDisposable { deactivate = (): void => { logger.info(`Deactivating Deephaven extension`); + this._mcpServer?.stop(); }; /** @@ -408,6 +420,86 @@ export class ExtensionController implements IDisposable { this._context.subscriptions.push(this._userLoginController); }; + /** + * Initialize MCP server for AI assistant integration. + * Server is started independently to work with both VS Code Copilot and external tools like Windsurf. + */ + initializeMCPServer = async (): Promise => { + assertDefined(this._coreJsApiCache, 'coreJsApiCache'); + assertDefined(this._panelService, 'panelService'); + assertDefined(this._pipServerController, 'pipServerController'); + assertDefined(this._pythonDiagnostics, 'pythonDiagnostics'); + assertDefined(this._pythonWorkspace, 'pythonWorkspace'); + assertDefined(this._serverManager, 'serverManager'); + assertDefined(this._outputChannel, 'outputChannel'); + assertDefined(this._outputChannelDebug, 'outputChannelDebug'); + + try { + // Create and start MCP server independently (not inside provider callback) + // This ensures server is running for external tools (Windsurf, Cline) that access via HTTP + this._mcpServer = new MCPServer( + this._coreJsApiCache, + this._outputChannel, + this._outputChannelDebug, + this._panelService, + this._pipServerController, + this._pythonDiagnostics, + this._pythonWorkspace, + this._serverManager + ); + + // Try to use previously stored port for consistency across sessions + const storedPort = this._context.workspaceState.get( + MCP_SERVER_PORT_STORAGE_KEY + ); + + const actualPort = await this._mcpServer.start(storedPort); + logger.info(`MCP Server started on port ${actualPort}`); + + // Store the port for next session (only if different from stored) + if (actualPort !== storedPort) { + await this._context.workspaceState.update( + MCP_SERVER_PORT_STORAGE_KEY, + actualPort + ); + } + + // Update status bar + this.updateMcpStatusBar(actualPort); + + // Auto-configure Windsurf MCP config if running in Windsurf + await this.updateWindsurfMcpConfig(actualPort); + + vscode.window.showInformationMessage( + `Deephaven MCP Server started on port ${actualPort}.` + ); + + // Register provider for VS Code Copilot + this._mcpServerDefinitionProvider = new McpServerDefinitionProvider( + this._mcpServer + ); + this._context.subscriptions.push(this._mcpServerDefinitionProvider); + + this._context.subscriptions.push( + vscode.lm.registerMcpServerDefinitionProvider( + 'deephaven-vscode.mcpServer', + this._mcpServerDefinitionProvider + ) + ); + + // Notify VS Code to refresh MCP tool cache. TBD: whether this is actually + // needed, but I've had some issues where tools seem to get cached and + // "stuck" as I've iterated on the extension. + this._mcpServerDefinitionProvider.refresh(); + } catch (error) { + logger.error('Failed to initialize MCP server:', error); + vscode.window.showErrorMessage( + `Failed to initialize MCP server: ${error instanceof Error ? error.message : String(error)}` + ); + // Don't fail extension activation if MCP server fails + } + }; + /** * Initialize diagnostics collections. */ @@ -698,6 +790,37 @@ export class ExtensionController implements IDisposable { getTempDir({ recreate: true }); }; + /** + * Initialize MCP status bar item. + */ + initializeStatusBar = (): void => { + this._mcpStatusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + 200 + ); + this._mcpStatusBarItem.command = COPY_MCP_URL_CMD; + this._context.subscriptions.push(this._mcpStatusBarItem); + }; + + /** + * Update MCP status bar with current port. + * @param port The port the MCP server is running on, or null to hide the status bar + */ + updateMcpStatusBar = (port: number | null): void => { + if (this._mcpStatusBarItem == null) { + return; + } + + if (port == null) { + this._mcpStatusBarItem.hide(); + return; + } + + this._mcpStatusBarItem.text = `$(dh-ext-logo) MCP: ${port}`; + this._mcpStatusBarItem.tooltip = `Deephaven MCP Server running on port ${port}. Click to copy URL.`; + this._mcpStatusBarItem.show(); + }; + /** * Register commands for the extension. */ @@ -707,6 +830,9 @@ export class ExtensionController implements IDisposable { /** Clear secret storage */ this.registerCommand(CLEAR_SECRET_STORAGE_CMD, this.onClearSecretStorage); + /** Copy MCP URL to clipboard */ + this.registerCommand(COPY_MCP_URL_CMD, this.onCopyMcpUrl); + /** Create new document */ this.registerCommand(CREATE_NEW_TEXT_DOC_CMD, this.onCreateNewDocument); @@ -879,7 +1005,7 @@ export class ExtensionController implements IDisposable { assertDefined(this._serverTreeView, 'serverManager'); vscode.window.onDidChangeWindowState( - this.maybeUpdateServerStatuses, + this.onWindowStateChange, undefined, this._context.subscriptions ); @@ -903,6 +1029,14 @@ export class ExtensionController implements IDisposable { this.maybeUpdateServerStatuses(); }; + /** + * Handle window state changes (focus/active). + */ + onWindowStateChange = (): void => { + this.maybeUpdateServerStatuses(); + this.maybeUpdateWindsurfMcpConfig(); + }; + /** * Update server statuses if vscode window is * active and focused. @@ -920,6 +1054,26 @@ export class ExtensionController implements IDisposable { this._pipServerController?.syncManagedServers(); }; + /** + * Check and update Windsurf MCP config if window gains focus. + * Only runs in Windsurf and when window is active and focused. + */ + maybeUpdateWindsurfMcpConfig = async (): Promise => { + const shouldUpdate = + vscode.window.state.active && vscode.window.state.focused; + + if (!shouldUpdate) { + return; + } + + const port = this._mcpServer?.getPort(); + if (port == null) { + return; + } + + await this.updateWindsurfMcpConfig(port); + }; + onDeleteVariable = async ( urlAndVariable: [URL, VariableDefintion] | undefined ): Promise => { @@ -931,6 +1085,7 @@ export class ExtensionController implements IDisposable { } const [url, variable] = urlAndVariable; + const connectionState = this._serverManager?.getConnection(url); if (!isInstanceOf(connectionState, DhcService)) { return; @@ -943,6 +1098,7 @@ export class ExtensionController implements IDisposable { folderElementOrUri: | RemoteImportSourceTreeFolderElement | vscode.Uri + | vscode.Uri[] | undefined ): Promise => { // Sometimes view/item/context commands pass undefined instead of a value. @@ -956,12 +1112,15 @@ export class ExtensionController implements IDisposable { await this._pythonWorkspace.refresh(); - const uri = - folderElementOrUri instanceof vscode.Uri - ? folderElementOrUri - : folderElementOrUri.uri; + const uris = Array.isArray(folderElementOrUri) + ? folderElementOrUri + : folderElementOrUri instanceof vscode.Uri + ? [folderElementOrUri] + : [folderElementOrUri.uri]; - this._pythonWorkspace.markFolder(uri); + for (const uri of uris) { + this._pythonWorkspace.markFolder(uri); + } }; onRemoveRemoteFileSource = async ( @@ -999,6 +1158,135 @@ export class ExtensionController implements IDisposable { this._toaster?.info('Stored secrets have been removed.'); }; + /** + * Handle copying MCP URL to clipboard. + */ + onCopyMcpUrl = async (): Promise => { + const port = this._mcpServer?.getPort(); + if (port == null) { + vscode.window.showWarningMessage('MCP Server is not running.'); + return; + } + + const mcpUrl = `http://localhost:${port}/mcp`; + await vscode.env.clipboard.writeText(mcpUrl); + + // Windsurf uses a separate MCP config file - automatically add/update the server entry + const isWindsurf = vscode.env.appName.toLowerCase().includes('windsurf'); + if (isWindsurf) { + const updated = await this.updateWindsurfMcpConfig(port); + if (updated) { + vscode.window.showInformationMessage( + `MCP URL copied and Windsurf config updated with '${MCP_SERVER_NAME}' server.` + ); + } else { + vscode.window.showInformationMessage( + `MCP URL copied to clipboard: ${mcpUrl}` + ); + } + } else { + vscode.window.showInformationMessage( + `MCP URL copied to clipboard: ${mcpUrl}` + ); + } + }; + + /** + * Update Windsurf MCP config with the Deephaven server entry. + * @param port The port the MCP server is running on + * @returns true if the config was updated, false otherwise + */ + private updateWindsurfMcpConfig = async (port: number): Promise => { + const isWindsurf = vscode.env.appName.toLowerCase().includes('windsurf'); + if (!isWindsurf) { + return false; + } + + const mcpUrl = `http://localhost:${port}/mcp`; + const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? ''; + const windsurfMcpConfigPath = `${homeDir}/.codeium/windsurf/mcp_config.json`; + const configUri = vscode.Uri.file(windsurfMcpConfigPath); + + try { + // Read existing config or create new one + let config: { mcpServers?: Record } = { + mcpServers: {}, + }; + try { + const existingContent = await vscode.workspace.fs.readFile(configUri); + config = JSON.parse(existingContent.toString()); + if (config.mcpServers == null) { + config.mcpServers = {}; + } + } catch { + // File doesn't exist or is invalid - start fresh + } + + // Check if config already has the correct entry + const existingEntry = config.mcpServers![MCP_SERVER_NAME]; + if (existingEntry?.serverUrl === mcpUrl) { + // Config is already up to date + return false; + } + + // Check if user has enabled auto-update in settings + const autoUpdate = this._config.getMcpAutoUpdateConfig(); + + if (!autoUpdate) { + let message: string; + let buttons: string[]; + + if (existingEntry == null) { + message = `Add '${MCP_SERVER_NAME}' to your Windsurf MCP config?`; + buttons = ['Yes', 'No']; + } else { + message = `Your Windsurf MCP config doesn't match this workspace's '${MCP_SERVER_NAME}'. Update to port ${port}?`; + buttons = ['Yes', 'Always', 'No']; + } + + const response = await vscode.window.showInformationMessage( + message, + ...buttons + ); + + if (response === 'Always') { + // Store the preference to auto-update in the future + await this._config.setMcpAutoUpdateConfig(true); + } else if (response !== 'Yes') { + return false; + } + + await vscode.window.showTextDocument(configUri); + } + + // Add/update the Deephaven MCP server entry + config.mcpServers![MCP_SERVER_NAME] = { + serverUrl: mcpUrl, + }; + + // Ensure parent directory exists + const configDir = vscode.Uri.file(`${homeDir}/.codeium/windsurf`); + try { + await vscode.workspace.fs.createDirectory(configDir); + } catch { + // Directory may already exist + } + + // Write updated config + await vscode.workspace.fs.writeFile( + configUri, + Buffer.from(JSON.stringify(config, null, 2)) + ); + + return true; + } catch (error) { + vscode.window.showErrorMessage( + `Failed to update Windsurf MCP config: ${error instanceof Error ? error.message : String(error)}` + ); + return false; + } + }; + /** * Create a new text document based on the given connection capabilities. * @param dhService @@ -1137,12 +1425,15 @@ export class ExtensionController implements IDisposable { * @param languageId Optional languageId to run the code as. If none provided, * use the languageId of the editor. */ - onRunCode: (...args: RunCodeCmdArgs) => Promise = async ( + onRunCode: ( + ...args: RunCodeCmdArgs + ) => Promise = async ( uri?: vscode.Uri, _arg?: { groupId: number }, constrainTo?: 'selection' | vscode.Range[], - languageId?: string - ): Promise => { + languageId?: string, + connectionUrl?: URL + ): Promise => { assertDefined(this._connectionController, 'connectionController'); if (uri == null) { @@ -1157,14 +1448,20 @@ export class ExtensionController implements IDisposable { } const connectionState = - await this._connectionController.getOrCreateConnection(uri, languageId); + await this._connectionController.getOrCreateConnection( + uri, + languageId, + connectionUrl + ); if (isInstanceOf(connectionState, DhcService)) { const ranges: readonly vscode.Range[] | undefined = constrainTo === 'selection' ? editor.selections : constrainTo; - await connectionState?.runCode(editor.document, languageId, ranges); + return connectionState.runCode(editor.document, languageId, ranges); } + + return null; }; /** diff --git a/src/mcp/MCPServer.ts b/src/mcp/MCPServer.ts new file mode 100644 index 000000000..79f498d21 --- /dev/null +++ b/src/mcp/MCPServer.ts @@ -0,0 +1,233 @@ +import * as vscode from 'vscode'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import * as http from 'http'; +import type { + IPanelService, + IServerManager, + McpTool, + McpToolSpec, +} from '../types'; +import type { PipServerController } from '../controllers'; +import type { OutputChannelWithHistory } from '../util'; +import { MCP_SERVER_NAME } from '../common'; +import { createRunCodeTool } from './tools/runCode'; +import { createRunCodeFromUriTool } from './tools/runCodeFromUri'; +import { createListPanelVariablesTool } from './tools/listPanelVariables'; +import { createListConnectionsTool } from './tools/listConnections'; +import { createListServersTool } from './tools/listServers'; +import { createConnectToServerTool } from './tools/connectToServer'; +import { createSetEditorConnectionTool } from './tools/setEditorConnection'; +import { createOpenVariablePanelsTool } from './tools/openVariablePanels'; +import { createStartPipServerTool } from './tools/startPipServer'; +import { createCheckPythonEnvTool } from './tools/checkPythonEnvironment'; +import { createAddRemoteFileSourcesTool } from './tools/addRemoteFileSources'; +import { createOpenFilesInEditorTool } from './tools/openFilesInEditor'; +import { createShowOutputPanelTool } from './tools/showOutputPanel'; +import { createGetLogsTool } from './tools/getLogs'; +import { createQueryTableDataTool } from './tools/queryTableData'; +import { createGetColumnStatsTool } from './tools/getColumnStats'; +import { createGetTableStatsTool } from './tools/getTableStats'; +import type { FilteredWorkspace } from '../services'; +import type { IAsyncCacheService } from '../types'; + +/** + * MCP Server for Deephaven extension. + * Provides tools for AI assistants (like GitHub Copilot) to interact with Deephaven. + */ +export class MCPServer { + private server: McpServer; + private httpServer: http.Server | null = null; + private port: number | null = null; + private readonly coreJsApiCache: IAsyncCacheService; + private readonly outputChannel: vscode.OutputChannel; + private readonly outputChannelDebug: OutputChannelWithHistory; + private readonly panelService: IPanelService; + private readonly pipServerController: PipServerController; + private readonly pythonDiagnostics: vscode.DiagnosticCollection; + private readonly pythonWorkspace: FilteredWorkspace; + private readonly serverManager: IServerManager; + + constructor( + coreJsApiCache: IAsyncCacheService, + outputChannel: vscode.OutputChannel, + outputChannelDebug: OutputChannelWithHistory, + panelService: IPanelService, + pipServerController: PipServerController, + pythonDiagnostics: vscode.DiagnosticCollection, + pythonWorkspace: FilteredWorkspace, + serverManager: IServerManager + ) { + this.coreJsApiCache = coreJsApiCache; + this.outputChannel = outputChannel; + this.outputChannelDebug = outputChannelDebug; + this.panelService = panelService; + this.pipServerController = pipServerController; + this.pythonDiagnostics = pythonDiagnostics; + this.pythonWorkspace = pythonWorkspace; + this.serverManager = serverManager; + + // Create an MCP server + this.server = new McpServer({ + name: MCP_SERVER_NAME, + version: '1.0.0', + }); + + this.registerTools(); + } + + private registerTool({ + name, + spec, + handler, + }: McpTool): void { + this.server.registerTool(name, spec, handler); + } + + private registerTools(): void { + this.registerTool(createRunCodeTool(this.serverManager)); + this.registerTool( + createRunCodeFromUriTool( + this.pythonDiagnostics, + this.pythonWorkspace, + this.serverManager + ) + ); + this.registerTool( + createListPanelVariablesTool(this.panelService, this.serverManager) + ); + this.registerTool(createListConnectionsTool(this.serverManager)); + this.registerTool(createListServersTool(this.serverManager)); + this.registerTool(createConnectToServerTool(this.serverManager)); + this.registerTool(createSetEditorConnectionTool(this.serverManager)); + this.registerTool(createOpenVariablePanelsTool(this.serverManager)); + this.registerTool(createStartPipServerTool(this.pipServerController)); + this.registerTool(createCheckPythonEnvTool(this.pipServerController)); + this.registerTool(createAddRemoteFileSourcesTool()); + this.registerTool(createOpenFilesInEditorTool()); + this.registerTool( + createShowOutputPanelTool(this.outputChannel, this.outputChannelDebug) + ); + this.registerTool(createGetLogsTool(this.outputChannelDebug)); + this.registerTool( + createQueryTableDataTool(this.coreJsApiCache, this.serverManager) + ); + this.registerTool(createGetColumnStatsTool(this.serverManager)); + this.registerTool(createGetTableStatsTool(this.serverManager)); + } + + /** + * Start the MCP server on an HTTP endpoint. + * Creates a new transport for each request (stateless operation). + * + * @param preferredPort Optional port to try first. If not provided or unavailable, will auto-allocate. + * @returns The actual port the server is listening on + */ + async start(preferredPort?: number): Promise { + const portToTry = preferredPort ?? 0; + + return new Promise((resolve, reject) => { + this.httpServer = http.createServer(async (req, res) => { + if (req.url === '/mcp' && req.method === 'POST') { + // Collect the request body + let body = ''; + req.on('data', chunk => { + body += chunk.toString(); + }); + + req.on('end', async () => { + try { + const requestBody = JSON.parse(body); + + // Create a new transport for each request to prevent request ID collisions + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + enableJsonResponse: true, + }); + + res.on('close', () => { + transport.close(); + }); + + await this.server.connect(transport); + await transport.handleRequest(req, res, requestBody); + } catch (error) { + res.writeHead(500, { contentType: 'application/json' }); + res.end( + JSON.stringify({ + error: `Failed to process request: ${error instanceof Error ? error.message : String(error)}`, + }) + ); + } + }); + } else if (req.url === '/mcp' && req.method === 'GET') { + // Return 405 for GET requests to prevent SSE connection attempts + res.writeHead(405, { + contentType: 'text/plain', + allow: 'POST', + }); + res.end('Method Not Allowed'); + } else { + res.writeHead(404, { contentType: 'text/plain' }); + res.end('Not found'); + } + }); + + this.httpServer.listen(portToTry, () => { + // Get the actual port assigned by the OS (important when port is 0) + const address = this.httpServer!.address(); + if (address && typeof address !== 'string') { + this.port = address.port; + } + resolve(this.port!); + }); + + this.httpServer.on('error', (error: NodeJS.ErrnoException) => { + // If preferred port is in use, try auto-allocating + if ( + error.code === 'EADDRINUSE' && + preferredPort != null && + preferredPort !== 0 + ) { + this.httpServer?.close(); + this.httpServer = null; + // Retry with auto-allocated port + this.start(0).then(resolve).catch(reject); + } else { + vscode.window.showErrorMessage( + `Failed to start MCP server: ${error.message}` + ); + reject(error); + } + }); + }); + } + + /** + * Get the current port the server is listening on. + * @returns The port number, or null if the server is not running. + */ + getPort(): number | null { + return this.port; + } + + /** + * Stop the MCP server. + */ + async stop(): Promise { + if (this.httpServer) { + return new Promise((resolve, reject) => { + this.httpServer!.close(err => { + if (err) { + reject(err); + } else { + this.httpServer = null; + this.port = null; + resolve(); + } + }); + }); + } + } +} diff --git a/src/mcp/TESTING.md b/src/mcp/TESTING.md new file mode 100644 index 000000000..7bd4813ae --- /dev/null +++ b/src/mcp/TESTING.md @@ -0,0 +1,96 @@ +# Testing the MCP Server + +## Quick Start + +1. **Start the Extension**: Press `F5` to launch in debug mode +2. **Verify Server Started**: Look for notification "Deephaven MCP Server started on http://localhost:3000/mcp" +3. **Run Tests**: Use one of the methods below + +## Method 1: Using the Test Script + +```bash +./test-mcp.sh +``` + +This will test: +- Listing available tools +- Calling the runCode tool + +## Method 2: Manual curl Commands + +### List Available Tools +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + }' +``` + +Expected response: +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "tools": [ + { + "name": "runCode", + "description": "Execute code in a Deephaven session...", + "inputSchema": { ... } + } + ] + } +} +``` + +### Call runCode Tool +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "runCode", + "arguments": { + "languageId": "python" + } + } + }' +``` + +## Method 3: Configure Claude Desktop + +Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`): + +```json +{ + "mcpServers": { + "deephaven": { + "url": "http://localhost:3000/mcp" + } + } +} +``` + +Restart Claude Desktop, and you should see the `runCode` tool available. + +## Method 4: Test with VS Code Terminal + +1. Open a Python file in your workspace +2. Make sure you have a Deephaven connection active +3. From VS Code terminal, test with curl (as above) +4. The runCode tool should execute the current file or selection + +## Troubleshooting + +- **Server not starting**: Check the "Deephaven" output channel for errors +- **Port already in use**: Change the port in `ExtensionController.ts` (line 420) +- **No response**: Verify the extension is running with `F5` +- **Tool not found**: Make sure the server started successfully diff --git a/src/mcp/index.ts b/src/mcp/index.ts new file mode 100644 index 000000000..124bea6a3 --- /dev/null +++ b/src/mcp/index.ts @@ -0,0 +1 @@ +export { MCPServer } from './MCPServer'; diff --git a/src/mcp/tools/addRemoteFileSources.ts b/src/mcp/tools/addRemoteFileSources.ts new file mode 100644 index 000000000..5d4b49ca0 --- /dev/null +++ b/src/mcp/tools/addRemoteFileSources.ts @@ -0,0 +1,64 @@ +import * as vscode from 'vscode'; +import { ADD_REMOTE_FILE_SOURCE_CMD } from '../../common/commands'; +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; + +const spec = { + title: 'Add Remote File Sources', + description: 'Add one or more remote file source folders to the workspace.', + inputSchema: { + folderUris: z + .array(z.string()) + .describe('List of folder URIs to add as remote file sources.'), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type AddRemoteFileSourcesTool = McpTool; + +export function createAddRemoteFileSourcesTool(): AddRemoteFileSourcesTool { + return { + name: 'addRemoteFileSources', + spec, + handler: async ({ + folderUris, + }: { + folderUris: string[]; + }): Promise> => { + const startTime = performance.now(); + try { + const uris = folderUris.map(uri => + vscode.Uri.parse(uri.replace(/\/$/, '')) + ); + await vscode.commands.executeCommand(ADD_REMOTE_FILE_SOURCE_CMD, uris); + const output = { + success: true, + message: 'Remote file sources added successfully.', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to add remote file sources: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/checkPythonEnvironment.ts b/src/mcp/tools/checkPythonEnvironment.ts new file mode 100644 index 000000000..87807063c --- /dev/null +++ b/src/mcp/tools/checkPythonEnvironment.ts @@ -0,0 +1,72 @@ +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; +import type { PipServerController } from '../../controllers/PipServerController'; + +const spec = { + title: 'Check Python Environment', + description: + 'Check if the Python environment supports starting a Deephaven pip server.', + inputSchema: {}, + outputSchema: { + isAvailable: z.boolean(), + interpreterPath: z.string().optional(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type CheckPythonEnvTool = McpTool; + +export function createCheckPythonEnvTool( + pipServerController: PipServerController | null +): CheckPythonEnvTool { + return { + name: 'checkPythonEnvironment', + spec, + handler: async (): Promise> => { + const startTime = performance.now(); + if (!pipServerController) { + const output = { + isAvailable: false, + message: 'PipServerController not available', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + try { + const result = await pipServerController.checkPipInstall(); + const output = { + isAvailable: result.isAvailable, + interpreterPath: result.isAvailable + ? result.interpreterPath + : undefined, + message: result.isAvailable + ? 'Python environment is available for pip server.' + : 'Python environment is not available for pip server.', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + isAvailable: false, + message: `Failed to check Python environment: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/connectToServer.ts b/src/mcp/tools/connectToServer.ts new file mode 100644 index 000000000..bbba8890e --- /dev/null +++ b/src/mcp/tools/connectToServer.ts @@ -0,0 +1,93 @@ +import * as vscode from 'vscode'; +import { CONNECT_TO_SERVER_CMD } from '../../common/commands'; +import { z } from 'zod'; +import type { + IServerManager, + McpTool, + McpToolHandlerResult, +} from '../../types'; + +const spec = { + title: 'Connect to Server', + description: + 'Create a connection to a Deephaven server. The server must already be configured in the extension. For DHE (Enterprise) servers, this will create a new worker.', + inputSchema: { + url: z.string().describe('Server URL (e.g., "http://localhost:10000")'), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type ConnectToServerTool = McpTool; + +export function createConnectToServerTool( + serverManager: IServerManager +): ConnectToServerTool { + return { + name: 'connectToServer', + spec, + handler: async ({ url }: { url: string }): Promise => { + const startTime = performance.now(); + try { + let serverUrl: URL; + try { + serverUrl = new URL(url); + } catch (e) { + const output = { + success: false, + message: `Invalid server URL: '${url}'. Please provide a valid URL (e.g., 'http://localhost:10000'). If this was a server label, you can check the list of configured servers to find the corresponding URL.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + const server = serverManager.getServer(serverUrl); + if (!server) { + const output = { + success: false, + message: `Server not found: ${url}. Use listServers to see available servers.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + const serverState = { type: server.type, url: serverUrl }; + await vscode.commands.executeCommand( + CONNECT_TO_SERVER_CMD, + serverState + ); + const output = { + success: true, + message: `Connecting to ${server.type} server at ${url}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to connect to server: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/getColumnStats.ts b/src/mcp/tools/getColumnStats.ts new file mode 100644 index 000000000..a903feb6e --- /dev/null +++ b/src/mcp/tools/getColumnStats.ts @@ -0,0 +1,191 @@ +import { z } from 'zod'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; +import type { + McpTool, + McpToolHandlerResult, + IServerManager, +} from '../../types'; +import { DhcService } from '../../services'; +import { isInstanceOf } from '../../util'; + +const spec = { + title: 'Get Column Statistics', + description: + 'Get statistical information for a column in a Deephaven table. Returns statistics like min, max, average, and unique value counts. Useful for understanding data distribution and column characteristics.', + inputSchema: { + connectionUrl: z + .string() + .describe( + 'Connection URL of the Deephaven server (e.g., "http://localhost:10000")' + ), + tableName: z.string().describe('Name of the table containing the column'), + columnName: z.string().describe('Name of the column to get statistics for'), + }, + outputSchema: { + success: z.boolean(), + statistics: z + .record(z.unknown()) + .optional() + .describe( + 'Map of statistic names to their values (e.g., MIN, MAX, AVG, SUM, etc.)' + ), + uniqueValues: z + .record(z.number()) + .optional() + .describe( + 'Map of unique values to their counts (only for columns with 19 or fewer unique values)' + ), + message: z.string().optional(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type GetColumnStatsTool = McpTool; + +/** + * Get column statistics using JS API. + */ +export function createGetColumnStatsTool( + serverManager: IServerManager +): GetColumnStatsTool { + return { + name: 'getColumnStats', + spec, + handler: async ({ + connectionUrl, + tableName, + columnName, + }: { + connectionUrl: string; + tableName: string; + columnName: string; + }): Promise => { + const startTime = performance.now(); + try { + // Parse and validate connection URL + let serverUrl: URL; + try { + serverUrl = new URL(connectionUrl); + } catch (e) { + const output = { + success: false, + message: `Invalid connection URL: '${connectionUrl}'. Please provide a valid URL (e.g., 'http://localhost:10000').`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get connection for this server + const connection = serverManager.getConnection(serverUrl); + + if (!isInstanceOf(connection, DhcService)) { + const output = { + success: false, + message: `No active connection found for ${connectionUrl}. Use connectToServer to establish a connection first.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Ensure the connection is initialized + if (!connection.isInitialized) { + await connection.initSession(); + } + + // Get the session for table operations + const session = connection.getSession(); + + if (!session) { + const output = { + success: false, + message: `Unable to access session for ${connectionUrl}. Ensure the server is connected and initialized.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get the table by name + const table: DhcType.Table = await session.getObject({ + type: 'Table', + name: tableName, + }); + + try { + // Find the column + const column = table.findColumn(columnName); + + if (!column) { + const output = { + success: false, + message: `Column '${columnName}' not found in table '${tableName}'. Available columns: ${table.columns.map(c => c.name).join(', ')}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text', text: JSON.stringify(output, null, 2) }, + ], + structuredContent: output, + }; + } + + // Get column statistics + const columnStats: DhcType.ColumnStatistics = + await table.getColumnStatistics(column); + + // Convert statisticsMap to plain object + const statistics: Record = {}; + columnStats.statisticsMap.forEach((value, key) => { + statistics[key] = value; + }); + + // Convert uniqueValues Map to plain object + const uniqueValues: Record = {}; + columnStats.uniqueValues.forEach((count, value) => { + uniqueValues[value] = count; + }); + + const output = { + success: true, + statistics, + uniqueValues: + Object.keys(uniqueValues).length > 0 ? uniqueValues : undefined, + message: `Retrieved statistics for column '${columnName}' in table '${tableName}'`, + executionTimeMs: performance.now() - startTime, + }; + + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } finally { + table.close(); + } + } catch (error) { + const output = { + success: false, + message: + error instanceof Error + ? error.message + : 'Unknown error occurred while getting column statistics', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/getLogs.ts b/src/mcp/tools/getLogs.ts new file mode 100644 index 000000000..c2df04a87 --- /dev/null +++ b/src/mcp/tools/getLogs.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; +import type { OutputChannelWithHistory } from '../../util'; + +const spec = { + title: 'Get Logs', + description: + 'Get the log history from the Deephaven debug output channel. Returns all accumulated log messages.', + inputSchema: {}, + outputSchema: { + success: z.boolean(), + logs: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type GetLogsTool = McpTool; + +export function createGetLogsTool( + outputChannelDebug: OutputChannelWithHistory +): GetLogsTool { + return { + name: 'getLogs', + spec, + handler: async (): Promise> => { + const startTime = performance.now(); + try { + const history = outputChannelDebug.getHistory(); + const logs = history.join('\n'); + + const output = { + success: true, + logs, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + logs: '', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/getTableStats.ts b/src/mcp/tools/getTableStats.ts new file mode 100644 index 000000000..6e35d4065 --- /dev/null +++ b/src/mcp/tools/getTableStats.ts @@ -0,0 +1,167 @@ +import { z } from 'zod'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; +import type { + McpTool, + McpToolHandlerResult, + IServerManager, +} from '../../types'; +import { DhcService } from '../../services'; +import { isInstanceOf } from '../../util'; + +const spec = { + title: 'Get Table Schema and Statistics', + description: + 'Get schema information and basic statistics for a Deephaven table. Returns column names, types, descriptions, row count, and other table metadata. Useful for understanding table structure and planning queries.', + inputSchema: { + connectionUrl: z + .string() + .describe( + 'Connection URL of the Deephaven server (e.g., "http://localhost:10000")' + ), + tableName: z.string().describe('Name of the table to describe'), + }, + outputSchema: { + success: z.boolean(), + tableName: z.string().optional(), + size: z.number().optional().describe('Number of rows in the table'), + columns: z + .array( + z.object({ + name: z.string(), + type: z.string(), + description: z.string().optional(), + }) + ) + .optional() + .describe('Array of column definitions'), + isRefreshing: z + .boolean() + .optional() + .describe('Whether the table is refreshing (ticking)'), + message: z.string().optional(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type GetTableStatsTool = McpTool; + +/** + * Get table schema and basic statistics. + */ +export function createGetTableStatsTool( + serverManager: IServerManager +): GetTableStatsTool { + return { + name: 'getTableStats', + spec, + handler: async ({ + connectionUrl, + tableName, + }: { + connectionUrl: string; + tableName: string; + }): Promise => { + const startTime = performance.now(); + try { + // Parse and validate connection URL + let serverUrl: URL; + try { + serverUrl = new URL(connectionUrl); + } catch (e) { + const output = { + success: false, + message: `Invalid connection URL: '${connectionUrl}'. Please provide a valid URL (e.g., 'http://localhost:10000').`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get connection for this server + const connection = serverManager.getConnection(serverUrl); + + if (!isInstanceOf(connection, DhcService)) { + const output = { + success: false, + message: `No active connection found for ${connectionUrl}. Use connectToServer to establish a connection first.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Ensure the connection is initialized + if (!connection.isInitialized) { + await connection.initSession(); + } + + // Get the session for table operations + const session = connection.getSession(); + + if (!session) { + const output = { + success: false, + message: `Unable to access session for ${connectionUrl}. Ensure the server is connected and initialized.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get the table by name + const table: DhcType.Table = await session.getObject({ + type: 'Table', + name: tableName, + }); + + try { + // Extract column information + const columns = table.columns.map(col => ({ + name: col.name, + type: col.type, + ...(col.description && { description: col.description }), + })); + + const output = { + success: true, + tableName, + size: table.size, + columns, + isRefreshing: table.isRefreshing, + message: `Table '${tableName}' has ${table.size} rows and ${columns.length} columns`, + executionTimeMs: performance.now() - startTime, + }; + + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } finally { + table.close(); + } + } catch (error) { + const output = { + success: false, + message: + error instanceof Error + ? error.message + : 'Unknown error occurred while getting table statistics', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/listConnections.ts b/src/mcp/tools/listConnections.ts new file mode 100644 index 000000000..cbd78519b --- /dev/null +++ b/src/mcp/tools/listConnections.ts @@ -0,0 +1,88 @@ +import { z } from 'zod'; +import type { + IServerManager, + McpTool, + McpToolHandlerResult, +} from '../../types'; + +const spec = { + title: 'List Connections', + description: + 'List all active Deephaven connections, optionally filtered by server URL.', + inputSchema: { + serverUrl: z + .string() + .optional() + .describe( + 'Optional server URL to filter connections (e.g., "http://localhost:10000")' + ), + }, + outputSchema: { + success: z.boolean(), + connections: z + .array( + z.object({ + serverUrl: z.string(), + isConnected: z.boolean(), + isRunningCode: z.boolean().optional(), + tagId: z.string().optional(), + }) + ) + .optional(), + message: z.string().optional(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type ListConnectionsTool = McpTool; + +export function createListConnectionsTool( + serverManager: IServerManager +): ListConnectionsTool { + return { + name: 'listConnections', + spec, + handler: async ({ + serverUrl, + }: { + serverUrl?: string; + }): Promise => { + const startTime = performance.now(); + try { + const parsedUrl = serverUrl ? new URL(serverUrl) : undefined; + const rawConnections = serverManager.getConnections(parsedUrl); + const connections = rawConnections.map(connection => ({ + serverUrl: connection.serverUrl.toString(), + isConnected: connection.isConnected, + isRunningCode: connection.isRunningCode, + tagId: connection.tagId, + })); + const output = { + success: true, + connections, + message: `Found ${connections.length} connection(s)`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to list connections: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/listPanelVariables.ts b/src/mcp/tools/listPanelVariables.ts new file mode 100644 index 000000000..1f14d70d3 --- /dev/null +++ b/src/mcp/tools/listPanelVariables.ts @@ -0,0 +1,113 @@ +import * as vscode from 'vscode'; +import { CONNECT_TO_SERVER_CMD } from '../../common/commands'; +import { z } from 'zod'; +import type { + IPanelService, + IServerManager, + McpTool, + McpToolHandlerResult, +} from '../../types'; + +const spec = { + title: 'List Panel Variables', + description: 'List all panel variables for a given Deephaven connection URL.', + inputSchema: { + url: z + .string() + .describe('The connection URL (e.g., "http://localhost:10000")'), + }, + outputSchema: { + success: z.boolean(), + variables: z + .array(z.object({ id: z.string(), title: z.string(), type: z.string() })) + .optional(), + message: z.string().optional(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type CreateListPanelVariablesTool = McpTool; + +export function createListPanelVariablesTool( + panelService: IPanelService, + serverManager: IServerManager +): CreateListPanelVariablesTool { + return { + name: 'listPanelVariables', + spec, + handler: async ({ url }: { url: string }): Promise => { + const startTime = performance.now(); + try { + const parsedUrl = new URL(url); + const connections = serverManager.getConnections(parsedUrl); + const hasConnection = connections.length > 0; + if (!hasConnection) { + const matchPort = + parsedUrl.hostname === 'localhost' || + parsedUrl.hostname === '127.0.0.1'; + const server = serverManager.getServer(parsedUrl, matchPort); + if (!server) { + const output = { + success: false, + message: `Server not found: ${url}. Use listServers to see available servers.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text' as const, text: JSON.stringify(output) }, + ], + structuredContent: output, + }; + } + if (server.type === 'DHC') { + const serverState = { type: server.type, url: server.url }; + await vscode.commands.executeCommand( + CONNECT_TO_SERVER_CMD, + serverState + ); + } else { + const output = { + success: false, + message: `No active connection to ${url}. Use connectToServer first.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text' as const, text: JSON.stringify(output) }, + ], + structuredContent: output, + }; + } + } + const variables = [...panelService.getVariables(parsedUrl)].map( + ({ id, title, type }) => ({ id, title, type }) + ); + const output = { + success: true, + variables, + message: `Found ${variables.length} panel variable(s)`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to list panel variables: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/listServers.ts b/src/mcp/tools/listServers.ts new file mode 100644 index 000000000..01b15cdb7 --- /dev/null +++ b/src/mcp/tools/listServers.ts @@ -0,0 +1,129 @@ +import { z } from 'zod'; +import type { + IServerManager, + McpTool, + McpToolHandlerResult, +} from '../../types'; + +const spec = { + title: 'List Servers', + description: + 'List all Deephaven servers with optional filtering by running status, connection status, or type.', + inputSchema: { + isRunning: z + .boolean() + .optional() + .describe('Filter by running status (true = running, false = stopped)'), + hasConnections: z + .boolean() + .optional() + .describe( + 'Filter by connection status (true = has connections, false = no connections)' + ), + type: z + .enum(['DHC', 'DHE']) + .optional() + .describe('Filter by server type (DHC = Community, DHE = Enterprise)'), + }, + outputSchema: { + success: z.boolean(), + servers: z + .array( + z.object({ + type: z.string(), + url: z.string(), + label: z.string().optional(), + isConnected: z.boolean(), + isRunning: z.boolean(), + connectionCount: z.number(), + isManaged: z.boolean().optional(), + tags: z.array(z.string()).optional(), + connections: z + .array( + z.object({ + isConnected: z.boolean(), + isRunningCode: z.boolean().optional(), + serverUrl: z.string(), + tagId: z.string().optional(), + }) + ) + .optional(), + }) + ) + .optional(), + message: z.string().optional(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type CreateListServersTool = McpTool; + +export function createListServersTool( + serverManager: IServerManager +): CreateListServersTool { + return { + name: 'listServers', + spec, + handler: async ({ + isRunning, + hasConnections, + type, + }: { + isRunning?: boolean; + hasConnections?: boolean; + type?: 'DHC' | 'DHE'; + }): Promise => { + const startTime = performance.now(); + try { + const servers = serverManager + .getServers({ isRunning, hasConnections, type }) + .map(server => { + const connections = serverManager + .getConnections(server.url) + .map(conn => ({ + isConnected: conn.isConnected, + isRunningCode: conn.isRunningCode, + serverUrl: conn.serverUrl.toString(), + tagId: conn.tagId ? String(conn.tagId) : undefined, + })); + return { + type: server.type, + url: server.url.toString(), + label: server.label, + isConnected: server.isConnected, + isRunning: server.isRunning, + connectionCount: server.connectionCount, + isManaged: server.isManaged, + tags: server.isManaged ? ['pip', 'managed'] : [], + connections, + }; + }); + const output = { + success: true, + servers, + message: `Found ${servers.length} server(s)`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to list servers: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/openFilesInEditor.ts b/src/mcp/tools/openFilesInEditor.ts new file mode 100644 index 000000000..31bbec511 --- /dev/null +++ b/src/mcp/tools/openFilesInEditor.ts @@ -0,0 +1,75 @@ +import * as vscode from 'vscode'; +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; + +const spec = { + title: 'Open Files in Editor', + description: 'Open one or more files in the VS Code editor.', + inputSchema: { + uris: z + .array(z.string()) + .describe('List of file URIs to open in the editor.'), + preview: z + .boolean() + .optional() + .describe('Open in preview mode (default: true).'), + preserveFocus: z + .boolean() + .optional() + .describe('Preserve focus in the current editor group (default: false).'), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type OpenFilesInEditorTool = McpTool; + +export function createOpenFilesInEditorTool(): OpenFilesInEditorTool { + return { + name: 'openFilesInEditor', + spec, + handler: async ({ + uris, + preview = true, + preserveFocus = false, + }: { + uris: string[]; + preview?: boolean; + preserveFocus?: boolean; + }): Promise> => { + const startTime = performance.now(); + try { + for (const uriStr of uris) { + const uri = vscode.Uri.parse(uriStr); + await vscode.window.showTextDocument(uri, { preview, preserveFocus }); + } + const output = { + success: true, + message: 'Files opened in editor successfully.', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to open files: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/openVariablePanels.ts b/src/mcp/tools/openVariablePanels.ts new file mode 100644 index 000000000..d41ced6ed --- /dev/null +++ b/src/mcp/tools/openVariablePanels.ts @@ -0,0 +1,135 @@ +import * as vscode from 'vscode'; +import { CONNECT_TO_SERVER_CMD, OPEN_VARIABLE_PANELS_CMD } from '../../common'; +import { z } from 'zod'; +import type { + McpTool, + McpToolHandlerResult, + IServerManager, +} from '../../types'; + +const spec = { + title: 'Open Variable Panels', + description: + 'Open variable panels for a given connection URL and list of variables.', + inputSchema: { + connectionUrl: z.string().describe('The Deephaven connection URL.'), + + variables: z + .array( + z.object({ + id: z.string(), + title: z.string(), + type: z.string(), + }) + ) + .describe('List of variable definitions to open panels for.'), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type OpenVariablePanelsTool = McpTool; + +export function createOpenVariablePanelsTool( + serverManager: IServerManager +): OpenVariablePanelsTool { + return { + name: 'openVariablePanels', + spec, + handler: async ({ + connectionUrl, + variables, + }: { + connectionUrl: string; + variables: { id: string; title: string; type: string }[]; + }): Promise> => { + const startTime = performance.now(); + try { + const parsedUrl = new URL(connectionUrl); + let connections = serverManager.getConnections(parsedUrl); + if (!connections.length) { + // Try to connect (DHC only, for DHE user must use connectToServer first) + const server = serverManager.getServer(parsedUrl); + if (!server) { + const output = { + success: false, + message: `Server not found: ${connectionUrl}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text' as const, text: JSON.stringify(output) }, + ], + structuredContent: output, + }; + } + if (server.type === 'DHC') { + const serverState = { type: server.type, url: server.url }; + await vscode.commands.executeCommand( + CONNECT_TO_SERVER_CMD, + serverState + ); + // Wait for connection to be established (could poll or just re-fetch) + connections = serverManager.getConnections(parsedUrl); + if (!connections.length) { + const output = { + success: false, + message: `Failed to connect to server: ${connectionUrl}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text' as const, text: JSON.stringify(output) }, + ], + structuredContent: output, + }; + } + } else { + const output = { + success: false, + message: `No active connection to ${connectionUrl}. Use connectToServer first.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [ + { type: 'text' as const, text: JSON.stringify(output) }, + ], + structuredContent: output, + }; + } + } + await vscode.commands.executeCommand( + OPEN_VARIABLE_PANELS_CMD, + parsedUrl, + variables + ); + const output = { + success: true, + message: 'Variable panels opened successfully', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to open variable panels: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/queryTableData.ts b/src/mcp/tools/queryTableData.ts new file mode 100644 index 000000000..83054daf3 --- /dev/null +++ b/src/mcp/tools/queryTableData.ts @@ -0,0 +1,520 @@ +import { z } from 'zod'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; +import type { + McpTool, + McpToolHandlerResult, + IServerManager, + IAsyncCacheService, +} from '../../types'; +import { DhcService } from '../../services'; +import { isInstanceOf } from '../../util'; + +const sortDirectionSchema = z.enum(['asc', 'desc']); + +const sortSpecSchema = z.object({ + column: z.string().describe('Column name to sort by'), + direction: sortDirectionSchema + .optional() + .default('asc') + .describe('Sort direction (asc or desc)'), +}); + +const filterOperationSchema = z.enum([ + 'eq', + 'eqIgnoreCase', + 'notEq', + 'notEqIgnoreCase', + 'greaterThan', + 'lessThan', + 'greaterThanOrEqualTo', + 'lessThanOrEqualTo', + 'in', + 'inIgnoreCase', + 'notIn', + 'notInIgnoreCase', + 'contains', + 'containsIgnoreCase', + 'matches', + 'matchesIgnoreCase', + 'isTrue', + 'isFalse', + 'isNull', +]); + +const filterValueTypeSchema = z.enum([ + 'string', + 'number', + 'boolean', + 'datetime', +]); + +const filterSpecSchema = z.object({ + column: z.string().describe('Column name to filter'), + operation: filterOperationSchema.describe('Filter operation to apply'), + value: z + .any() + .optional() + .describe('Value to filter by (not needed for isNull, isTrue, isFalse)'), + valueType: filterValueTypeSchema + .optional() + .describe( + 'Type of the filter value (string, number, boolean, datetime). Required if value is provided. Note: datetime uses ofNumber() with timestamp as ofDateTime() is not yet supported.' + ), +}); + +const aggregationOperationSchema = z.enum([ + 'Count', + 'Min', + 'Max', + 'Sum', + 'Avg', + 'Var', + 'Std', + 'First', + 'Last', + 'Skip', +]); + +const aggregationSpecSchema = z.object({ + column: z.string().describe('Column name to aggregate'), + operation: aggregationOperationSchema.describe( + 'Aggregation operation to apply' + ), +}); + +const queryConfigSchema = z.object({ + filters: z + .array(filterSpecSchema) + .optional() + .describe('Array of filter specifications to apply to the table'), + groupBy: z + .array(z.string()) + .optional() + .describe('Column names to group by for aggregations'), + aggregations: z + .array(aggregationSpecSchema) + .optional() + .describe( + 'Array of aggregation specifications. Each specifies a column and operation.' + ), + defaultOperation: aggregationOperationSchema + .optional() + .describe( + 'Default aggregation operation for columns not specified in aggregations' + ), + sortBy: z + .array(sortSpecSchema) + .optional() + .describe( + 'Array of sort specifications. Applied in order: first sort is primary, second is secondary, etc.' + ), +}); + +const spec = { + title: 'Query Table Data', + description: + 'Query data from a Deephaven table with support for filtering, sorting, aggregations, and row limiting. Returns data in a format easily represented as a table or values in chat.', + inputSchema: { + connectionUrl: z + .string() + .describe( + 'Connection URL of the Deephaven server (e.g., "http://localhost:10000")' + ), + tableName: z + .string() + .describe('Name of the table to query (must exist in the session)'), + query: queryConfigSchema + .optional() + .describe( + 'Query configuration including filters, sorts, and aggregations. All fields are optional.' + ), + maxRows: z + .number() + .int() + .positive() + .max(10000) + .optional() + .default(100) + .describe( + 'Maximum number of rows to return (default: 100, hard limit: 10000). Set lower for large tables to avoid overwhelming responses.' + ), + }, + outputSchema: { + success: z.boolean(), + data: z + .array(z.record(z.unknown())) + .optional() + .describe('Array of row objects with column values'), + columns: z + .array( + z.object({ + name: z.string(), + type: z.string(), + }) + ) + .optional() + .describe('Column metadata (name and type)'), + rowCount: z.number().optional().describe('Number of rows returned'), + totalRows: z + .number() + .optional() + .describe('Total rows in table (before maxRows limit)'), + executionTimeMs: z + .number() + .optional() + .describe('Query execution time in milliseconds'), + message: z.string().optional(), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type QueryTableDataTool = McpTool; + +/** + * Create a FilterValue from a value and type. + * Uses Deephaven FilterValue factory methods: ofString, ofNumber, ofBoolean. + * Note: ofDateTime is not yet supported, so datetime values use ofNumber with timestamp. + */ +function createFilterValue( + dh: typeof DhcType, + value: unknown, + valueType: string +): DhcType.FilterValue { + switch (valueType) { + case 'string': + return dh.FilterValue.ofString(String(value)); + case 'number': + return dh.FilterValue.ofNumber(Number(value)); + case 'boolean': + return dh.FilterValue.ofBoolean(Boolean(value)); + case 'datetime': + // ofDateTime() is not yet supported per Deephaven docs + // For DateTime column types, ofNumber() works with values from Row.get + // For ISO string dates, convert to timestamp + const dateValue = + value instanceof Date ? value : new Date(value as string | number); + return dh.FilterValue.ofNumber(dateValue.getTime()); + default: + throw new Error(`Unsupported filter value type: ${valueType}`); + } +} + +/** + * Convert flattened aggregations array to operationMap format for getTotalsTable. + */ +function buildOperationMap( + aggregations: Array<{ + column: string; + operation: string; + }> +): Record { + const operationMap: Record = {}; + + for (const agg of aggregations) { + if (!operationMap[agg.column]) { + operationMap[agg.column] = []; + } + operationMap[agg.column].push(agg.operation); + } + + return operationMap; +} + +/** + * Helper to format column values for JSON serialization. + * Handles special types that don't serialize well (BigInt, Date, etc.) + */ +function formatValue(value: unknown): unknown { + if (value === null || value === undefined) { + return null; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + // Handle other special types as needed + return value; +} + +/** + * Get table data using JS API client-side operations. + */ +export function createQueryTableDataTool( + coreJsApiCache: IAsyncCacheService, + serverManager: IServerManager +): QueryTableDataTool { + return { + name: 'queryTableData', + spec, + handler: async ({ + connectionUrl, + tableName, + query, + maxRows = 100, + }: { + connectionUrl: string; + tableName: string; + query?: { + filters?: Array<{ + column: string; + operation: string; + value?: unknown; + valueType?: string; + }>; + groupBy?: string[]; + aggregations?: Array<{ column: string; operation: string }>; + defaultOperation?: string; + sortBy?: Array<{ + column: string; + direction?: 'asc' | 'desc'; + }>; + }; + maxRows?: number; + }): Promise => { + const startTime = performance.now(); + try { + // Parse and validate connection URL + let serverUrl: URL; + try { + serverUrl = new URL(connectionUrl); + } catch (e) { + const output = { + success: false, + message: `Invalid connection URL: '${connectionUrl}'. Please provide a valid URL (e.g., 'http://localhost:10000').`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get connection for this server + const connection = serverManager.getConnection(serverUrl); + + if (!isInstanceOf(connection, DhcService)) { + const output = { + success: false, + message: `No active connection found for ${connectionUrl}. Use connectToServer to establish a connection first.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Ensure the connection is initialized + if (!connection.isInitialized) { + await connection.initSession(); + } + + // Get the session for table operations + const session = connection.getSession(); + + if (!session) { + const output = { + success: false, + message: `Unable to access session for ${connectionUrl}. Ensure the server is connected and initialized.`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + + // Get the base table by name + const baseTable: DhcType.Table = await session.getObject({ + type: 'Table', + name: tableName, + }); + + let workingTable: DhcType.Table | DhcType.TotalsTable = baseTable; + + try { + // Get dh API from cache for filter operations (needed for FilterValue) + const dh = await coreJsApiCache.get(serverUrl); + + // Apply filters if specified + if (query?.filters && query.filters.length > 0) { + const filterConditions: DhcType.FilterCondition[] = + query.filters.map(filter => { + const column = workingTable.findColumn(filter.column); + const columnFilter = column.filter(); + + // Operations that don't need a value + if (filter.operation === 'isNull') { + return columnFilter.isNull(); + } + if (filter.operation === 'isTrue') { + return columnFilter.isTrue(); + } + if (filter.operation === 'isFalse') { + return columnFilter.isFalse(); + } + + // Operations that need a value + if (!filter.value || !filter.valueType) { + throw new Error( + `Filter operation '${filter.operation}' requires 'value' and 'valueType' fields` + ); + } + + // Operations that take arrays + if ( + ['in', 'inIgnoreCase', 'notIn', 'notInIgnoreCase'].includes( + filter.operation + ) + ) { + if (!Array.isArray(filter.value)) { + throw new Error( + `Filter operation '${filter.operation}' requires an array value` + ); + } + const filterValues = filter.value.map((v: unknown) => + createFilterValue(dh, v, filter.valueType!) + ); + return ( + columnFilter as unknown as Record< + string, + ( + ...args: DhcType.FilterValue[] + ) => DhcType.FilterCondition + > + )[filter.operation](...filterValues); + } + + // Single value operations + const filterValue = createFilterValue( + dh, + filter.value, + filter.valueType + ); + return ( + columnFilter as unknown as Record< + string, + (arg: DhcType.FilterValue) => DhcType.FilterCondition + > + )[filter.operation](filterValue); + }); + + // Apply filters and wait for table to update + workingTable.applyFilter(filterConditions); + await new Promise(resolve => { + const handler = (): void => { + workingTable.removeEventListener('filterchanged', handler); + resolve(); + }; + workingTable.addEventListener('filterchanged', handler); + }); + } + + // Create totals table if aggregation config provided + if ( + query?.groupBy || + query?.aggregations || + query?.defaultOperation + ) { + const config: Partial = {}; + + if (query.groupBy) { + config.groupBy = query.groupBy; + } + + if (query.aggregations && query.aggregations.length > 0) { + config.operationMap = buildOperationMap(query.aggregations); + } + + if (query.defaultOperation) { + config.defaultOperation = query.defaultOperation; + } + + workingTable = await baseTable.getTotalsTable( + config as DhcType.TotalsTableConfig + ); + } + + // Apply client-side sorting if specified + if (query?.sortBy && query.sortBy.length > 0) { + const sorts: DhcType.Sort[] = query.sortBy.map(sort => { + const column = workingTable.findColumn(sort.column); + return sort.direction === 'desc' + ? column.sort().desc() + : column.sort().asc(); + }); + // Apply sort and wait for table to update + workingTable.applySort(sorts); + await new Promise(resolve => { + const handler = (): void => { + workingTable.removeEventListener('sortchanged', handler); + resolve(); + }; + workingTable.addEventListener('sortchanged', handler); + }); + } + + // Get table data + const totalRows = workingTable.size; + const rowsToFetch = Math.min(maxRows, totalRows); + + // Set viewport and get data + workingTable.setViewport(0, rowsToFetch - 1); + const viewportData = await workingTable.getViewportData(); + + // Extract column information + const columns = workingTable.columns.map(col => ({ + name: col.name, + type: col.type, + })); + + // Convert rows to JSON objects + const data = viewportData.rows.map(row => { + const rowObj: Record = {}; + for (const col of workingTable.columns) { + const value = row.get(col); + rowObj[col.name] = formatValue(value); + } + return rowObj; + }); + + const output = { + success: true, + data, + columns, + rowCount: data.length, + totalRows, + executionTimeMs: performance.now() - startTime, + message: + data.length < totalRows + ? `Showing ${data.length} of ${totalRows} rows (limited by maxRows=${maxRows})` + : `Showing all ${totalRows} rows`, + }; + + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } finally { + // Always close table subscriptions to free resources + if (workingTable !== baseTable) { + workingTable.close(); + } + baseTable.close(); + } + } catch (error) { + const output = { + success: false, + message: `Error getting table data: ${error instanceof Error ? error.message : String(error)}`, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/runCode.ts b/src/mcp/tools/runCode.ts new file mode 100644 index 000000000..ede5a68ab --- /dev/null +++ b/src/mcp/tools/runCode.ts @@ -0,0 +1,159 @@ +import * as vscode from 'vscode'; +import { CONNECT_TO_SERVER_CMD } from '../../common/commands'; +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; +import type { IServerManager } from '../../types'; +import { DhcService } from '../../services'; +import { isInstanceOf } from '../../util'; +import { + runCodeOutputSchema, + createResult, + extractVariables, +} from './runCodeUtils'; + +const spec = { + title: 'Run Deephaven Code', + description: + 'Execute arbitrary code text in a Deephaven session. Use this for ad-hoc script execution. For running code from workspace files, use runCodeFromUri instead.', + inputSchema: { + code: z.string().describe('The code text to execute.'), + languageId: z + .string() + .describe('The language ID for the code. Must be "python" or "groovy".'), + connectionUrl: z + .string() + .optional() + .describe('The Deephaven connection URL to use for execution.'), + }, + outputSchema: runCodeOutputSchema, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type RunCodeTool = McpTool; + +export function createRunCodeTool(serverManager: IServerManager): RunCodeTool { + return { + name: 'runCode', + spec, + handler: async ({ + code, + languageId, + connectionUrl, + }: { + code: string; + languageId: string; + connectionUrl?: string; + }): Promise => { + const startTime = performance.now(); + try { + // Validate languageId + if (languageId !== 'python' && languageId !== 'groovy') { + return createResult( + false, + [], + `Invalid languageId: '${languageId}'. Must be "python" or "groovy".`, + performance.now() - startTime + ); + } + + // If connectionUrl is provided, ensure connection exists + if (connectionUrl) { + let parsedUrl: URL; + try { + parsedUrl = new URL(connectionUrl); + } catch (e) { + return createResult( + false, + [], + `Invalid connectionUrl: '${connectionUrl}'. Please provide a valid Deephaven server URL (e.g., 'http://localhost:10000'). If this was a server label, use listServers to find the corresponding URL.`, + performance.now() - startTime + ); + } + let connections = serverManager.getConnections(parsedUrl); + if (!connections.length) { + // Try to connect (DHC only, for DHE user must use connectToServer first) + const server = serverManager.getServer(parsedUrl); + if (!server) { + return createResult( + false, + [], + `Server not found: ${connectionUrl}`, + performance.now() - startTime + ); + } + if (server.type === 'DHC') { + const serverState = { type: server.type, url: server.url }; + await vscode.commands.executeCommand( + CONNECT_TO_SERVER_CMD, + serverState + ); + // Wait for connection to be established (could poll or just re-fetch) + connections = serverManager.getConnections(parsedUrl); + if (!connections.length) { + return createResult( + false, + [], + `Failed to connect to server: ${connectionUrl}`, + performance.now() - startTime + ); + } + } else { + return createResult( + false, + [], + `No active connection to ${connectionUrl}. Use connectToServer first.`, + performance.now() - startTime + ); + } + } + + const connection = connections[0]; + + // Verify it's a DHC connection + if (!isInstanceOf(connection, DhcService)) { + return createResult( + false, + [], + 'Code execution is only supported for DHC connections.', + performance.now() - startTime + ); + } + + // Execute the code + const result = await connection.runCode(code, languageId); + + // Extract variables from result (even if there was an error) + const variables = extractVariables(result); + + // Check for errors in the result + if (result != null && result.error) { + return createResult( + false, + variables, + `Code execution failed:\n${result.error}`, + performance.now() - startTime + ); + } + + return createResult(true, variables, undefined, performance.now() - startTime); + } else { + // No connectionUrl provided - need to get a default connection + return createResult( + false, + [], + 'connectionUrl is required. Use listConnections to find available connections.', + performance.now() - startTime + ); + } + } catch (error) { + return createResult( + false, + [], + `Failed to execute code: ${error instanceof Error ? error.message : String(error)}`, + performance.now() - startTime + ); + } + }, + }; +} diff --git a/src/mcp/tools/runCodeFromUri.ts b/src/mcp/tools/runCodeFromUri.ts new file mode 100644 index 000000000..097af8ece --- /dev/null +++ b/src/mcp/tools/runCodeFromUri.ts @@ -0,0 +1,202 @@ +import * as vscode from 'vscode'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; +import { RUN_CODE_COMMAND, type RunCodeCmdArgs } from '../../common/commands'; +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; + +import type { IServerManager } from '../../types'; +import { DhcService, type FilteredWorkspace } from '../../services'; +import { isInstanceOf } from '../../util'; +import { + runCodeOutputSchema, + createResult, + extractVariables, +} from './runCodeUtils'; + +const spec = { + title: 'Run Deephaven Code from URI', + description: + 'Execute code from a workspace file URI in a Deephaven session. Can run the entire file or constrain execution to the current selection within the file.', + inputSchema: { + uri: z.string().describe('The file URI to run.'), + constrainTo: z + .enum(['selection']) + .optional() + .describe( + 'Constrain execution to the current selection within the file specified by uri' + ), + languageId: z + .string() + .optional() + .describe( + 'The language ID (python, groovy) to use for execution. If not provided, inferred from the file.' + ), + connectionUrl: z + .string() + .optional() + .describe('The Deephaven connection URL to use for execution.'), + }, + outputSchema: runCodeOutputSchema, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type RunCodeFromUriTool = McpTool; + +export function createRunCodeFromUriTool( + pythonDiagnostics: vscode.DiagnosticCollection, + pythonWorkspace: FilteredWorkspace, + serverManager: IServerManager +): RunCodeFromUriTool { + return { + name: 'runCodeFromUri', + spec, + handler: async ({ + uri, + constrainTo, + languageId, + connectionUrl, + }: { + uri: string; + constrainTo?: 'selection'; + languageId?: string; + connectionUrl?: string; + }): Promise => { + const startTime = performance.now(); + try { + const parsedUri = vscode.Uri.parse(uri); + let parsedUrl: URL | undefined; + + if (connectionUrl) { + try { + parsedUrl = new URL(connectionUrl); + } catch (e) { + return createResult( + false, + [], + `Invalid connectionUrl: '${connectionUrl}'. Please provide a valid Deephaven server URL (e.g., 'http://localhost:10000').`, + performance.now() - startTime + ); + } + } + + const cmdArgs: RunCodeCmdArgs = [ + parsedUri, + undefined, + constrainTo, + languageId, + parsedUrl, + ]; + + const result = + await vscode.commands.executeCommand( + RUN_CODE_COMMAND, + ...cmdArgs + ); + + const errors = getDiagnosticsErrors(pythonDiagnostics); + if (errors.length > 0) { + // Look for 'No module named' errors and extract the module names + const noModuleErrors = new Set( + errors + .map(e => /^No module named '([^']+)'/.exec(e.message)?.[1]) + .filter(e => e != null) + ); + + let hint = ''; + if (noModuleErrors.size > 0) { + // Check if the remote file source plugin is installed + // Get the connection from the URI that was executed + const executedConnection = parsedUri + ? serverManager.getUriConnection(parsedUri) + : null; + const hasPlugin = + executedConnection != null && + isInstanceOf(executedConnection, DhcService) && + executedConnection.hasRemoteFileSourcePlugin(); + + if (!hasPlugin) { + hint = `\n\nHint: The Python remote file source plugin is not installed. Install it with 'pip install deephaven-plugin-python-remote-file-source' to enable importing workspace packages.`; + } else { + const foundUris: string[] = []; + + const rootNodes = pythonWorkspace.getChildNodes(null); + + for (const rootNode of rootNodes) { + for (const node of pythonWorkspace.iterateNodeTree( + rootNode.uri + )) { + if ( + node.type === 'folder' && + noModuleErrors.has(node.name) && + node.uri + ) { + foundUris.push(node.uri.toString()); + } + } + } + + if (foundUris.length > 0) { + hint = `\n\nHint: If this is a package in your workspace, try adding one of these folders as a remote file source using the addRemoteFileSources tool:\n${foundUris.map(u => `- ${u}`).join('\n')}`; + } else { + hint = `\n\nHint: If this is a package in your workspace, try adding its folder as a remote file source using the addRemoteFileSources tool.`; + } + } + } + const errorMsg = errors + .map( + e => + `${e.uri}: ${e.message} [${e.range.start.line + 1}:${e.range.start.character + 1}]` + ) + .join('\n'); + // Extract variables from result (before returning error) + const variables = extractVariables(result); + return createResult( + false, + variables, + `Code execution failed due to errors:\n${errorMsg}${hint}`, + performance.now() - startTime + ); + } + + // Extract variables from result + const variables = extractVariables(result); + + return createResult( + true, + variables, + undefined, + performance.now() - startTime + ); + } catch (error) { + return createResult( + false, + [], + `Failed to execute code: ${error instanceof Error ? error.message : String(error)}`, + performance.now() - startTime + ); + } + }, + }; +} + +function getDiagnosticsErrors(diagnostics: vscode.DiagnosticCollection): { + uri: string; + message: string; + range: vscode.Range; +}[] { + const diagnosticsMap = new Map([...diagnostics]); + const errors: { uri: string; message: string; range: vscode.Range }[] = []; + for (const [uri, diags] of diagnosticsMap) { + for (const diag of diags) { + if (diag.severity === vscode.DiagnosticSeverity.Error) { + errors.push({ + uri: uri.toString(), + message: diag.message, + range: diag.range, + }); + } + } + } + return errors; +} diff --git a/src/mcp/tools/runCodeUtils.ts b/src/mcp/tools/runCodeUtils.ts new file mode 100644 index 000000000..b76ad5dba --- /dev/null +++ b/src/mcp/tools/runCodeUtils.ts @@ -0,0 +1,91 @@ +import { z } from 'zod'; +import type { dh as DhcType } from '@deephaven/jsapi-types'; + +/** + * Schema for variable results returned after code execution. + */ +export const variableResultSchema = z.object({ + id: z.string(), + title: z.string(), + type: z.string(), + isNew: z + .boolean() + .describe('True if the variable was created, false if it was updated'), +}); + +export type VariableResult = z.infer; + +/** + * Common output schema for runCode and runCodeFromUri tools. + */ +export const runCodeOutputSchema = { + success: z.boolean(), + message: z.string(), + variables: z + .array(variableResultSchema) + .optional() + .describe('Variables created or updated by the code execution'), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), +}; + +export type RunCodeOutput = { + success: boolean; + message: string; + variables?: VariableResult[]; + executionTimeMs?: number; +}; + +/** + * Creates a result for code execution. + */ +export function createResult( + success: boolean, + variables: VariableResult[], + message?: string, + executionTimeMs?: number +): { + content: { type: 'text'; text: string }[]; + structuredContent: RunCodeOutput; +} { + const output: RunCodeOutput = { + success, + message: + message ?? + (success ? 'Code executed successfully' : 'Code execution failed'), + variables: variables.length > 0 ? variables : undefined, + executionTimeMs, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; +} + +/** + * Extracts variables from a code execution result. + */ +export function extractVariables( + result: DhcType.ide.CommandResult | null | undefined +): VariableResult[] { + if (result == null) { + return []; + } + + return [ + ...result.changes.created.map(v => ({ + id: String(v.id), + title: v.title ?? v.id, + type: v.type, + isNew: true, + })), + ...result.changes.updated.map(v => ({ + id: String(v.id), + title: v.title ?? v.id, + type: v.type, + isNew: false, + })), + ]; +} diff --git a/src/mcp/tools/setEditorConnection.ts b/src/mcp/tools/setEditorConnection.ts new file mode 100644 index 000000000..2f02e3000 --- /dev/null +++ b/src/mcp/tools/setEditorConnection.ts @@ -0,0 +1,90 @@ +import * as vscode from 'vscode'; +import { z } from 'zod'; +import type { + IServerManager, + McpTool, + McpToolHandlerResult, +} from '../../types'; + +const spec = { + title: 'Set Editor Connection', + description: + 'Set the connection for a given editor by URI and connection URL.', + inputSchema: { + uri: z.string().describe('The file URI of the editor.'), + connectionUrl: z.string().describe('The Deephaven connection URL.'), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type HandlerResult = McpToolHandlerResult; +type SetEditorConnectionTool = McpTool; + +export function createSetEditorConnectionTool( + serverManager: IServerManager +): SetEditorConnectionTool { + return { + name: 'setEditorConnection', + spec, + handler: async ({ + uri, + connectionUrl, + }: { + uri: string; + connectionUrl: string; + }): Promise => { + const startTime = performance.now(); + try { + const parsedUri = vscode.Uri.parse(uri); + const parsedUrl = new URL(connectionUrl); + const connections = serverManager.getConnections(parsedUrl); + if (!connections.length) { + const output = { + success: false, + message: 'No active connection for the given URL', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + const connection = connections[0]; + const doc = await vscode.workspace.openTextDocument(parsedUri); + const languageId = doc.languageId; + await serverManager.setEditorConnection( + parsedUri, + languageId, + connection + ); + const output = { + success: true, + message: 'Editor connection set successfully', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to set editor connection: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/showOutputPanel.ts b/src/mcp/tools/showOutputPanel.ts new file mode 100644 index 000000000..db704016d --- /dev/null +++ b/src/mcp/tools/showOutputPanel.ts @@ -0,0 +1,71 @@ +import * as vscode from 'vscode'; +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; +import type { OutputChannelWithHistory } from '../../util'; + +const spec = { + title: 'Show Output Panel', + description: + 'Show a Deephaven output panel in the VS Code UI. Can show either the regular output channel or debug channel.', + inputSchema: { + channel: z + .enum(['output', 'debug']) + .optional() + .describe( + 'Which output channel to show: "output" for the regular Deephaven output, or "debug" for the debug output. Defaults to "output".' + ), + }, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type ShowOutputPanelTool = McpTool; + +export function createShowOutputPanelTool( + outputChannel: vscode.OutputChannel, + outputChannelDebug: OutputChannelWithHistory +): ShowOutputPanelTool { + return { + name: 'showOutputPanel', + spec, + handler: async ({ + channel = 'output', + }): Promise> => { + const startTime = performance.now(); + try { + if (channel === 'output') { + outputChannel.show(true); + } else { + outputChannelDebug.show(true); + } + + const output = { + success: true, + message: `Deephaven ${channel} panel shown successfully.`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to show output panel: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/mcp/tools/startPipServer.ts b/src/mcp/tools/startPipServer.ts new file mode 100644 index 000000000..2178d08bb --- /dev/null +++ b/src/mcp/tools/startPipServer.ts @@ -0,0 +1,68 @@ +import { z } from 'zod'; +import type { McpTool, McpToolHandlerResult } from '../../types'; +import type { PipServerController } from '../../controllers/PipServerController'; + +const spec = { + title: 'Start Pip Server', + description: + 'Start a managed Deephaven pip server if the environment supports it.', + inputSchema: {}, + outputSchema: { + success: z.boolean(), + message: z.string(), + executionTimeMs: z + .number() + .optional() + .describe('Execution time in milliseconds'), + }, +} as const; + +type Spec = typeof spec; +type StartPipServerTool = McpTool; + +export function createStartPipServerTool( + pipServerController: PipServerController +): StartPipServerTool { + return { + name: 'startPipServer', + spec, + handler: async (): Promise> => { + const startTime = performance.now(); + try { + const result = await pipServerController.checkPipInstall(); + if (!result.isAvailable) { + const output = { + success: false, + message: + 'Pip server environment is not available. The `deephaven-server` package may not be installed.', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + await pipServerController.startServer(); + const output = { + success: true, + message: 'Pip server started successfully.', + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } catch (error) { + const output = { + success: false, + message: `Failed to start pip server: ${error instanceof Error ? error.message : String(error)}`, + executionTimeMs: performance.now() - startTime, + }; + return { + content: [{ type: 'text', text: JSON.stringify(output) }], + structuredContent: output, + }; + } + }, + }; +} diff --git a/src/providers/McpServerDefinitionProvider.ts b/src/providers/McpServerDefinitionProvider.ts new file mode 100644 index 000000000..28ca7b748 --- /dev/null +++ b/src/providers/McpServerDefinitionProvider.ts @@ -0,0 +1,51 @@ +import * as vscode from 'vscode'; +import { MCP_SERVER_NAME } from '../common'; +import type { MCPServer } from '../mcp/MCPServer'; + +/** + * Provides MCP server definitions to VS Code Copilot. + * This allows Copilot to discover and connect to the Deephaven MCP server. + */ +export class McpServerDefinitionProvider + implements vscode.McpServerDefinitionProvider +{ + private readonly _onDidChangeMcpServerDefinitions = + new vscode.EventEmitter(); + readonly onDidChangeMcpServerDefinitions = + this._onDidChangeMcpServerDefinitions.event; + + constructor(private readonly mcpServer: MCPServer) {} + + async provideMcpServerDefinitions(): Promise< + vscode.McpHttpServerDefinition[] + > { + const port = this.mcpServer.getPort(); + if (port == null) { + return []; + } + + return [ + new vscode.McpHttpServerDefinition( + MCP_SERVER_NAME, + vscode.Uri.parse(`http://localhost:${port}/mcp`), + { + // eslint-disable-next-line @typescript-eslint/naming-convention + API_VERSION: '1.0.0', + }, + '1.0.0' + ), + ]; + } + + /** + * Notify VS Code that MCP server definitions have changed. + * Call this after the MCP server starts or when the port changes. + */ + refresh(): void { + this._onDidChangeMcpServerDefinitions.fire(); + } + + dispose(): void { + this._onDidChangeMcpServerDefinitions.dispose(); + } +} diff --git a/src/providers/index.ts b/src/providers/index.ts index f6e30badd..7846bf92c 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,5 @@ export * from './CreateQueryViewProvider'; +export * from './McpServerDefinitionProvider'; export * from './RemoteImportSourceTreeProvider'; export * from './RunCommandCodeLensProvider'; export * from './RunMarkdownCodeBlockCodeLensProvider'; diff --git a/src/services/ConfigService.ts b/src/services/ConfigService.ts index fbc307b51..4c5c04ac5 100644 --- a/src/services/ConfigService.ts +++ b/src/services/ConfigService.ts @@ -70,9 +70,23 @@ function hasValidURL({ url }: { url: string }): boolean { } } +function getMcpAutoUpdateConfig(): boolean { + return getConfig().get(CONFIG_KEY.mcpAutoUpdateConfig, false); +} + +async function setMcpAutoUpdateConfig(value: boolean): Promise { + await getConfig().update( + CONFIG_KEY.mcpAutoUpdateConfig, + value, + true // global scope + ); +} + // eslint-disable-next-line @typescript-eslint/naming-convention export const ConfigService: IConfigService = { getCoreServers, getEnterpriseServers, isElectronFetchEnabled, + getMcpAutoUpdateConfig, + setMcpAutoUpdateConfig, }; diff --git a/src/services/DhcService.ts b/src/services/DhcService.ts index 4e0be9cf2..392b3890c 100644 --- a/src/services/DhcService.ts +++ b/src/services/DhcService.ts @@ -149,6 +149,14 @@ export class DhcService extends DisposableBase implements IDhcService { return this.cn != null && this.session != null; } + /** + * Get the IdeSession for table operations and queries. + * Returns null if session is not initialized. + */ + getSession(): DhcType.IdeSession | null { + return this.session; + } + private _isRunningCode = false; get isRunningCode(): boolean { return this._isRunningCode; @@ -363,6 +371,14 @@ export class DhcService extends DisposableBase implements IDhcService { return client; } + /** + * Check if the remote file source plugin is installed. + * @returns true if the plugin is installed, false otherwise + */ + hasRemoteFileSourcePlugin = (): boolean => { + return this.remoteFileSourcePluginSubscription != null; + }; + getConsoleTypes = async (): Promise> => { if (this.cn == null) { return new Set(); @@ -405,35 +421,40 @@ export class DhcService extends DisposableBase implements IDhcService { } async runCode( - document: vscode.TextDocument, + documentOrText: vscode.TextDocument | string, languageId: string, ranges?: readonly vscode.Range[] - ): Promise { - // Clear previous diagnostics when cmd starts running - this.diagnosticsCollection.set(document.uri, []); + ): Promise { + if (typeof documentOrText !== 'string') { + // Clear previous diagnostics when cmd starts running + this.diagnosticsCollection.set(documentOrText.uri, []); + } if (this.session == null) { await this.initSession(); } if (this.cn == null || this.cnId == null || this.session == null) { - return; + return null; } const [consoleType] = await this.cn.getConsoleTypes(); if (consoleType !== languageId) { this.toaster.error(`This connection does not support '${languageId}'.`); - return; + return null; } - const text = ranges - ? getCombinedRangeLinesText(document, ranges) - : document.getText(); + const text = + typeof documentOrText === 'string' + ? documentOrText + : ranges + ? getCombinedRangeLinesText(documentOrText, ranges) + : documentOrText.getText(); logger.info('Sending text to dh:', text); - let result: DhcType.ide.CommandResult; + let result: DhcType.ide.CommandResult | null = null; let error: string | null = null; try { @@ -464,7 +485,7 @@ export class DhcService extends DisposableBase implements IDhcService { this.toaster.error( 'Session is no longer valid. Please re-run the command to reconnect.' ); - return; + return null; } } @@ -473,9 +494,9 @@ export class DhcService extends DisposableBase implements IDhcService { // Note we shouldn't have to log the error to the output channel since code // execution errors should already get captured via the server output. this.outputChannel.show(true); - this.toaster.error('An error occurred when running a command'); + // this.toaster.error('An error occurred when running a command'); - if (languageId === 'python') { + if (languageId === 'python' && typeof documentOrText !== 'string') { const errors = parseServerError(error); for (const { type, file, line, value } of errors) { @@ -492,7 +513,7 @@ export class DhcService extends DisposableBase implements IDhcService { // Zero length will flag a token instead of a line const lineLength = - fileLine < 0 ? 0 : document.lineAt(fileLine).text.length; + fileLine < 0 ? 0 : documentOrText.lineAt(fileLine).text.length; // Diagnostic representing the line of code that produced the server error const diagnostic: vscode.Diagnostic = { @@ -505,20 +526,20 @@ export class DhcService extends DisposableBase implements IDhcService { this.diagnosticsCollection.set( file == null || file === '' - ? document.uri + ? documentOrText.uri : vscode.Uri.parse(file), [diagnostic] ); } } } - - return; } + assertDefined(result, 'result'); + const changed = [ - ...result!.changes.created, - ...result!.changes.updated, + ...result.changes.created, + ...result.changes.updated, // Type assertion is necessary to make use of our more specific branded types // coming from the less specific types defined in the jsapi-types package. ] as VariableDefintion[]; @@ -542,6 +563,8 @@ export class DhcService extends DisposableBase implements IDhcService { showVariables ); } + + return result; } } diff --git a/src/services/ServerManager.ts b/src/services/ServerManager.ts index 7422dc413..133ba8f19 100644 --- a/src/services/ServerManager.ts +++ b/src/services/ServerManager.ts @@ -478,11 +478,36 @@ export class ServerManager implements IServerManager { }; /** - * Get all connections. + * Get all connections. Optionally filter connections by server or worker URL. + * @param serverOrWorkerUrl The server or worker URL to filter connections by. * @returns An array of all connections. */ - getConnections = (): ConnectionState[] => { - return [...this._connectionMap.values()]; + getConnections = (serverOrWorkerUrl?: URL): ConnectionState[] => { + if (serverOrWorkerUrl == null) { + return [...this._connectionMap.values()]; + } + + if (this._connectionMap.has(serverOrWorkerUrl)) { + return [this._connectionMap.getOrThrow(serverOrWorkerUrl)]; + } + + const server = this.getServer(serverOrWorkerUrl); + if (server == null) { + return []; + } + + if (server.type === 'DHC') { + const connection = this._connectionMap.get(serverOrWorkerUrl); + return connection != null ? [connection] : []; + } + + // For DHE, return all connections associated with the server URL + return [...this._connectionMap.values()].filter(connection => { + const dheServerUrl = + this._workerURLToServerURLMap.get(connection.serverUrl) ?? + connection.serverUrl; + return dheServerUrl.toString() === serverOrWorkerUrl.toString(); + }); }; /** diff --git a/src/types/index.ts b/src/types/index.ts index 110f93a6b..239f7e12b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ export type * from './commonTypes'; export type * from './global'; +export type * from './mcpTypes'; export type * from './remoteFileSourceTypes'; export type * from './remoteImportSourceTreeTypes'; export type * from './serviceTypes'; diff --git a/src/types/mcpTypes.d.ts b/src/types/mcpTypes.d.ts new file mode 100644 index 000000000..14c10ecb6 --- /dev/null +++ b/src/types/mcpTypes.d.ts @@ -0,0 +1,25 @@ +import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp'; +import type { ZodRawShape } from 'zod'; + +export interface McpToolSpec< + InputSchema extends ZodRawShape = ZodRawShape, + OutputSchema extends ZodRawShape = ZodRawShape, +> { + title: string; + description: string; + inputSchema: InputSchema; + outputSchema: OutputSchema; +} + +export type McpToolHandler = + ToolCallback; + +export type McpToolHandlerResult = Awaited< + ReturnType> +>; + +export type McpTool = { + name: string; + spec: Spec; + handler: McpToolHandler; +}; diff --git a/src/types/serviceTypes.d.ts b/src/types/serviceTypes.d.ts index 1b475f7fc..bf9a3794d 100644 --- a/src/types/serviceTypes.d.ts +++ b/src/types/serviceTypes.d.ts @@ -41,6 +41,8 @@ export interface IConfigService { isElectronFetchEnabled: () => boolean; getCoreServers: () => CoreConnectionConfig[]; getEnterpriseServers: () => EnterpriseConnectionConfig[]; + getMcpAutoUpdateConfig: () => boolean; + setMcpAutoUpdateConfig: (value: boolean) => Promise; } /** @@ -56,6 +58,7 @@ export interface IDhcService extends IDisposable, ConnectionState { initSession(): Promise; getClient(): Promise; + getSession(): DhcType.IdeSession | null; getConsoleTypes: () => Promise>; supportsConsoleType: (consoleType: ConsoleType) => Promise; @@ -63,7 +66,7 @@ export interface IDhcService extends IDisposable, ConnectionState { document: vscode.TextDocument, languageId: string, ranges?: readonly vscode.Range[] - ) => Promise; + ) => Promise; } export interface IDheService extends ConnectionState, IDisposable { @@ -168,7 +171,7 @@ export interface IServerManager extends IDisposable { hasConnectionUris: (connection: ConnectionState) => boolean; getConnection: (serverUrl: URL) => ConnectionState | undefined; - getConnections: () => ConnectionState[]; + getConnections: (serverOrWorkerUrl?: URL) => ConnectionState[]; getConnectionUris: (connection: ConnectionState) => vscode.Uri[]; getEditorConnection: (uri: vscode.Uri) => Promise; getWorkerCredentials: ( diff --git a/src/util/OutputChannelWithHistory.ts b/src/util/OutputChannelWithHistory.ts index b2336f4c7..79639584a 100644 --- a/src/util/OutputChannelWithHistory.ts +++ b/src/util/OutputChannelWithHistory.ts @@ -47,6 +47,13 @@ export class OutputChannelWithHistory implements vscode.OutputChannel { this.history = []; }; + /** + * Get the history as an array of strings. + */ + getHistory = (): readonly string[] => { + return this.history; + }; + downloadHistoryToFile = async (): Promise => { const response = await vscode.window.showSaveDialog({ defaultUri: vscode.Uri.file( diff --git a/test-mcp.sh b/test-mcp.sh new file mode 100755 index 000000000..2326977b8 --- /dev/null +++ b/test-mcp.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Test script for MCP server + +echo "Testing MCP Server..." +echo "" + +# Test 1: List available tools +echo "1. Listing available tools..." +curl -X POST http://localhost:63307/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + }' | jq . > test.json + +echo "" +echo "" + +# Test 2: Call runCode tool (this will fail without a proper URI, but will show it's working) +echo "2. Calling runCode tool..." +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "runCode", + "arguments": { + "languageId": "python" + } + } + }' | jq . + +echo "" diff --git a/test-mcp2.sh b/test-mcp2.sh new file mode 100755 index 000000000..2f05056f5 --- /dev/null +++ b/test-mcp2.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# From your test script, or manually: +curl -X POST http://localhost:51520/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' diff --git a/test.json b/test.json new file mode 100644 index 000000000..e771d548a --- /dev/null +++ b/test.json @@ -0,0 +1,243 @@ +{ + "result": { + "tools": [ + { + "name": "runCode", + "title": "Run Deephaven Code", + "description": "Execute code in a Deephaven session. Runs the code from a file or the current selection.", + "inputSchema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "description": "The file URI to run. If not provided, runs the active editor." + }, + "constrainTo": { + "type": "string", + "enum": [ + "selection" + ], + "description": "Constrain execution to current selection" + }, + "languageId": { + "type": "string", + "description": "The language ID (python, groovy) to use for execution" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + } + }, + "required": [ + "success", + "message" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "listPanelVariables", + "title": "List Panel Variables", + "description": "List all panel variables for a given Deephaven connection URL.", + "inputSchema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The connection URL (e.g., \"http://localhost:10000\")" + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "variables": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "title", + "type" + ], + "additionalProperties": false + } + }, + "message": { + "type": "string" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "listConnections", + "title": "List Connections", + "description": "List all active Deephaven connections.", + "inputSchema": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "connections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "serverUrl": { + "type": "string" + }, + "isConnected": { + "type": "boolean" + }, + "isRunningCode": { + "type": "boolean" + }, + "tagId": { + "type": "string" + } + }, + "required": [ + "serverUrl", + "isConnected" + ], + "additionalProperties": false + } + }, + "message": { + "type": "string" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + { + "name": "listServers", + "title": "List Servers", + "description": "List all Deephaven servers with optional filtering by running status, connection status, or type.", + "inputSchema": { + "type": "object", + "properties": { + "isRunning": { + "type": "boolean", + "description": "Filter by running status (true = running, false = stopped)" + }, + "hasConnections": { + "type": "boolean", + "description": "Filter by connection status (true = has connections, false = no connections)" + }, + "type": { + "type": "string", + "enum": [ + "DHC", + "DHE" + ], + "description": "Filter by server type (DHC = Community, DHE = Enterprise)" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "outputSchema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "servers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "label": { + "type": "string" + }, + "isConnected": { + "type": "boolean" + }, + "isRunning": { + "type": "boolean" + }, + "connectionCount": { + "type": "number" + }, + "isManaged": { + "type": "boolean" + } + }, + "required": [ + "type", + "url", + "isConnected", + "isRunning", + "connectionCount" + ], + "additionalProperties": false + } + }, + "message": { + "type": "string" + } + }, + "required": [ + "success" + ], + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + } + } + ] + }, + "jsonrpc": "2.0", + "id": 1 +}