diff --git a/package-lock.json b/package-lock.json index 102519b..21bec45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@mongodb-js/devtools-connect": "^3.7.2", "@mongosh/service-provider-node-driver": "^3.6.0", "bson": "^6.10.3", + "dockerode": "^4.0.6", "lru-cache": "^11.1.0", "mongodb": "^6.15.0", "mongodb-connection-string-url": "^3.0.2", @@ -34,6 +35,7 @@ "@jest/globals": "^29.7.0", "@modelcontextprotocol/inspector": "^0.10.2", "@redocly/cli": "^1.34.2", + "@types/dockerode": "^3.3.38", "@types/jest": "^29.5.14", "@types/node": "^22.14.0", "@types/simple-oauth2": "^5.0.7", @@ -1260,6 +1262,12 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -2023,6 +2031,69 @@ "dev": true, "license": "MIT" }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.3.tgz", + "integrity": "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@hapi/boom": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", @@ -2615,6 +2686,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jsep-plugin/assignment": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", @@ -3638,35 +3719,30 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", @@ -3677,35 +3753,30 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@radix-ui/number": { @@ -5499,6 +5570,29 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.38", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.38.tgz", + "integrity": "sha512-nnrcfUe2iR+RyOuz0B4bZgQwD9djQa9ADEjp7OAgBs10pYT0KSCtplJjcmBDJz0qaReX5T7GbE5i4VplvzUHvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -5578,6 +5672,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.97", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.97.tgz", + "integrity": "sha512-4r3Y9EuCJjWduiam85Fo4GBQtneaEuoaBSdiKo+o6qwQUh0JFVBe7cRUK6I6yVzA0S1gBJJfoQx4VtBH4e5ikg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -5947,7 +6068,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5957,7 +6077,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6194,7 +6313,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, "funding": [ { "type": "github", @@ -6455,7 +6573,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, "funding": [ { "type": "github", @@ -6823,7 +6940,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -6836,7 +6952,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, "license": "MIT" }, "node_modules/colorette": { @@ -7648,6 +7763,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/docker-modem": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", + "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.15.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.6.tgz", + "integrity": "sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w==", + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.6", + "protobufjs": "^7.3.2", + "tar-fs": "~2.1.2", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/dompurify": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", @@ -7731,7 +7892,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -7747,7 +7907,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -7861,7 +8020,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -8705,7 +8863,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "devOptional": true, "license": "MIT" }, "node_modules/fs-minipass": { @@ -8779,7 +8936,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -9164,7 +9320,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, "funding": [ { "type": "github", @@ -9370,7 +9525,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10508,6 +10662,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -10525,7 +10685,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/loose-envify": { @@ -10849,8 +11008,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/mobx": { "version": "6.13.7", @@ -12360,7 +12518,6 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz", "integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==", - "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -12399,7 +12556,6 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "license": "MIT", - "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -12662,7 +12818,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "devOptional": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -12741,7 +12896,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13550,6 +13704,12 @@ "rxjs": "^7.8.1" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "license": "ISC" + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -13628,7 +13788,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "devOptional": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -13652,7 +13811,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13667,7 +13825,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -13962,7 +14119,6 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", "license": "MIT", - "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -13975,7 +14131,6 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", - "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -13986,15 +14141,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/tar-fs/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", - "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -14472,7 +14625,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -14607,7 +14759,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -14762,7 +14913,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -14832,7 +14982,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "devOptional": true, "license": "ISC", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 57b1fb3..be9ae12 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@jest/globals": "^29.7.0", "@modelcontextprotocol/inspector": "^0.10.2", "@redocly/cli": "^1.34.2", + "@types/dockerode": "^3.3.38", "@types/jest": "^29.5.14", "@types/node": "^22.14.0", "@types/simple-oauth2": "^5.0.7", @@ -65,6 +66,7 @@ "@mongodb-js/devtools-connect": "^3.7.2", "@mongosh/service-provider-node-driver": "^3.6.0", "bson": "^6.10.3", + "dockerode": "^4.0.6", "lru-cache": "^11.1.0", "mongodb": "^6.15.0", "mongodb-connection-string-url": "^3.0.2", diff --git a/src/common/local/dockerUtils.ts b/src/common/local/dockerUtils.ts new file mode 100644 index 0000000..b55ea58 --- /dev/null +++ b/src/common/local/dockerUtils.ts @@ -0,0 +1,88 @@ +import { promisify } from "util"; +import { exec } from "child_process"; +import { z } from "zod"; +import { DockerPsSummary } from "../../tools/local/localTool.js"; + +export interface ClusterDetails { + name: string; + mongodbVersion: string; + status: string; + health: string; + port: string; + dbUser: string; + isAuth: boolean; // Indicates if authentication is required +} + +export async function getValidatedClusterDetails(clusterName: string): Promise { + const execAsync = promisify(exec); + + try { + const { stdout } = await execAsync(`docker inspect ${clusterName}`); + const containerDetails = JSON.parse(stdout) as DockerPsSummary[]; + + if (!Array.isArray(containerDetails) || containerDetails.length === 0) { + throw new Error(`No details found for cluster "${clusterName}".`); + } + + const DockerInspectSchema = z.object({ + Config: z.object({ + Env: z.array(z.string()).optional(), + Image: z.string(), + }), + NetworkSettings: z.object({ + Ports: z.record( + z.string(), + z + .array( + z + .object({ + HostPort: z.string(), + }) + .optional() + ) + .optional() + ), + }), + State: z.object({ + Health: z + .object({ + Status: z.string(), + }) + .optional(), + Status: z.string(), + }), + Name: z.string(), + }); + + const validatedDetails = DockerInspectSchema.parse(containerDetails[0]); + + const port = validatedDetails.NetworkSettings.Ports["27017/tcp"]?.[0]?.HostPort || "Unknown"; + + const envVars = validatedDetails.Config.Env || []; + const username = envVars.find((env) => env.startsWith("MONGODB_INITDB_ROOT_USERNAME="))?.split("=")[1]; + + const isAuth = !!username; // Determine if authentication is required + + const mongodbVersionMatch = validatedDetails.Config.Image.match(/mongodb\/mongodb-atlas-local:(.+)/); + const mongodbVersion = mongodbVersionMatch ? mongodbVersionMatch[1] : "Unknown"; + + const status = validatedDetails.State.Status || "Unknown"; + const health = validatedDetails.State.Health?.Status || "Unknown"; + + return { + name: validatedDetails.Name.replace("/", ""), + mongodbVersion, + status, + health, + port, + dbUser: username || "No user found", + isAuth, + }; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to inspect cluster "${clusterName}": ${error.message}`); + } else { + throw new Error(`An unexpected error occurred while inspecting cluster "${clusterName}".`); + } + } +} diff --git a/src/server.ts b/src/server.ts index b0e8e19..fdbc652 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { Session } from "./session.js"; import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { AtlasTools } from "./tools/atlas/tools.js"; +import { LocalTools } from "./tools/local/tools.js"; import { MongoDbTools } from "./tools/mongodb/tools.js"; import logger, { initializeLogger, LogId } from "./logger.js"; import { ObjectId } from "mongodb"; @@ -134,7 +135,7 @@ export class Server { } private registerTools() { - for (const tool of [...AtlasTools, ...MongoDbTools]) { + for (const tool of [...AtlasTools, ...LocalTools, ...MongoDbTools]) { new tool(this.session, this.userConfig, this.telemetry).register(this.mcpServer); } } diff --git a/src/tools/local/create/createCluster.ts b/src/tools/local/create/createCluster.ts new file mode 100644 index 0000000..32caec0 --- /dev/null +++ b/src/tools/local/create/createCluster.ts @@ -0,0 +1,96 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +import * as net from "net"; +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { LocalToolBase } from "../localTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; + +export class CreateClusterTool extends LocalToolBase { + protected name = "local-create-cluster"; + protected description = "Create a new local MongoDB cluster"; + protected operationType: OperationType = "create"; + protected argsShape = { + name: z.string().describe("Name of the cluster"), + port: z.number().describe("The port number on which the local MongoDB cluster will run.").optional(), + }; + + protected async execute({ name, port }: ToolArgs): Promise { + const availablePort = await this.getAvailablePort(port); + const result = await this.createCluster(name, availablePort); + return this.formatCreateClusterResult(result); + } + + private async getAvailablePort(port?: number): Promise { + if (port) { + const isAvailable = await this.isPortAvailable(port); + if (!isAvailable) { + throw new Error(`Port ${port} is already in use. Please specify a different port.`); + } + return port; + } + + // Find a random available port + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.listen(0, () => { + const address = server.address(); + if (typeof address === "object" && address?.port) { + const randomPort = address.port; + server.close(() => resolve(randomPort)); + } else { + reject(new Error("Failed to find an available port.")); + } + }); + server.on("error", reject); + }); + } + + private async isPortAvailable(port: number): Promise { + return new Promise((resolve) => { + const server = net.createServer(); + server.once("error", () => resolve(false)); // Port is in use + server.once("listening", () => { + server.close(() => resolve(true)); // Port is available + }); + server.listen(port); + }); + } + + private async createCluster(clusterName: string, port: number): Promise<{ success: boolean; message: string }> { + const execAsync = promisify(exec); + + try { + // Run the Docker command to create a new MongoDB container + await execAsync(`docker run -d --name ${clusterName} -p ${port}:27017 mongodb/mongodb-atlas-local:8.0`); + + return { + success: true, + message: `Cluster "${clusterName}" created successfully on port ${port}.`, + }; + } catch (error) { + if (error instanceof Error) { + return { + success: false, + message: `Failed to create cluster "${clusterName}": ${error.message}`, + }; + } else { + return { + success: false, + message: `An unexpected error occurred while creating cluster "${clusterName}".`, + }; + } + } + } + + private formatCreateClusterResult(result: { success: boolean; message: string }): CallToolResult { + return { + content: [ + { + type: "text", + text: result.message, + }, + ], + }; + } +} diff --git a/src/tools/local/delete/deleteCluster.ts b/src/tools/local/delete/deleteCluster.ts new file mode 100644 index 0000000..fc5cfd1 --- /dev/null +++ b/src/tools/local/delete/deleteCluster.ts @@ -0,0 +1,65 @@ +import { promisify } from "util"; +import { exec } from "child_process"; +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { LocalToolBase } from "../localTool.js"; +import { OperationType } from "../../tool.js"; + +export class DeleteClusterTool extends LocalToolBase { + protected name = "local-delete-cluster"; + protected description = "Delete a local MongoDB cluster"; + protected operationType: OperationType = "delete"; + protected argsShape = { + clusterName: z.string().nonempty("Cluster name is required"), + }; + + protected async execute({ clusterName }: { clusterName: string }): Promise { + const result = await this.deleteCluster(clusterName); + return this.formatDeleteClusterResult(result, clusterName); + } + + private async deleteCluster(clusterName: string): Promise<{ success: boolean; message: string }> { + const execAsync = promisify(exec); + + try { + console.log(`Deleting MongoDB cluster with name: ${clusterName}`); + // Stop and remove the Docker container + await execAsync(`docker rm -f ${clusterName}`); + + return { + success: true, + message: `Cluster "${clusterName}" deleted successfully.`, + }; + } catch (error) { + if (error instanceof Error) { + console.error(`Failed to delete cluster "${clusterName}":`, error.message); + return { + success: false, + message: `Failed to delete cluster "${clusterName}": ${error.message}`, + }; + } else { + console.error(`Unexpected error while deleting cluster "${clusterName}":`, error); + return { + success: false, + message: `An unexpected error occurred while deleting cluster "${clusterName}".`, + }; + } + } + } + + private formatDeleteClusterResult( + result: { success: boolean; message: string }, + clusterName: string + ): CallToolResult { + return { + content: [ + { + type: "text", + text: result.success + ? `Cluster "${clusterName}" has been deleted.` + : `Failed to delete cluster "${clusterName}": ${result.message}`, + }, + ], + }; + } +} diff --git a/src/tools/local/localTool.ts b/src/tools/local/localTool.ts new file mode 100644 index 0000000..9553935 --- /dev/null +++ b/src/tools/local/localTool.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; +import { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js"; +import logger, { LogId } from "../../logger.js"; +import { ToolBase, ToolCategory, TelemetryToolMetadata } from "../tool.js"; + +export interface DockerPsSummary { + ID: string; + Image: string; + Command: string; + CreatedAt: string; + RunningFor: string; + Ports: string; + Status: string; + Names: string; +} + +export abstract class LocalToolBase extends ToolBase { + protected category: ToolCategory = "local"; + + protected resolveTelemetryMetadata( + ...args: Parameters> + ): TelemetryToolMetadata { + const toolMetadata: TelemetryToolMetadata = {}; + if (!args.length) { + return toolMetadata; + } + + // Create a typed parser for the exact shape we expect + const argsShape = z.object(this.argsShape); + const parsedResult = argsShape.safeParse(args[0]); + + if (!parsedResult.success) { + logger.debug( + LogId.telemetryMetadataError, + "tool", + `Error parsing tool arguments: ${parsedResult.error.message}` + ); + return toolMetadata; + } + + const data = parsedResult.data; + + // Extract projectId using type guard + if ("projectId" in data && typeof data.projectId === "string" && data.projectId.trim() !== "") { + toolMetadata.projectId = data.projectId; + } + + // Extract orgId using type guard + if ("orgId" in data && typeof data.orgId === "string" && data.orgId.trim() !== "") { + toolMetadata.orgId = data.orgId; + } + return toolMetadata; + } +} diff --git a/src/tools/local/metadata/connectCluster.ts b/src/tools/local/metadata/connectCluster.ts new file mode 100644 index 0000000..abc7b94 --- /dev/null +++ b/src/tools/local/metadata/connectCluster.ts @@ -0,0 +1,76 @@ +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import logger, { LogId } from "../../../logger.js"; +import { getValidatedClusterDetails } from "../../../common/local/dockerUtils.js"; +import { LocalToolBase } from "../localTool.js"; +import { OperationType } from "../../tool.js"; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export class ConnectClusterTool extends LocalToolBase { + protected name = "local-connect-cluster"; + protected description = "Connect to a local MongoDB cluster"; + protected operationType: OperationType = "metadata"; + protected argsShape = { + clusterName: z.string().describe("Local cluster name"), + password: z.string().optional().describe("Password for the cluster if authentication is required"), + }; + + protected async execute({ + clusterName, + password, + }: { + clusterName: string; + password?: string; + }): Promise { + const clusterDetails = await getValidatedClusterDetails(clusterName); + + if (clusterDetails.isAuth && !password) { + throw new Error(`Cluster "${clusterName}" requires authentication. Please provide a password.`); + } + + const authPart = clusterDetails.isAuth ? `${clusterDetails.dbUser}:${password}@` : ""; + const connectionString = `mongodb://${authPart}localhost:${clusterDetails.port}/?directConnection=true`; + + return await this.connectToCluster(connectionString, clusterName); + } + + private async connectToCluster(connectionString: string, clusterName: string): Promise { + let lastError: Error | undefined = undefined; + + for (let i = 0; i < 20; i++) { + try { + await this.session.connectToMongoDB(connectionString, this.config.connectOptions); + lastError = undefined; + break; + } catch (err: unknown) { + const error = err instanceof Error ? err : new Error(String(err)); + + lastError = error; + + logger.debug( + LogId.atlasConnectFailure, + "atlas-connect-cluster", + `error connecting to cluster: ${error.message}` + ); + + await sleep(500); // wait for 500ms before retrying + } + } + + if (lastError) { + throw lastError; + } + + return { + content: [ + { + type: "text", + text: `Connected to cluster "${clusterName}" with connection string: ${connectionString}`, + }, + ], + }; + } +} diff --git a/src/tools/local/read/inspectCluster.ts b/src/tools/local/read/inspectCluster.ts new file mode 100644 index 0000000..86fc8df --- /dev/null +++ b/src/tools/local/read/inspectCluster.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { getValidatedClusterDetails, ClusterDetails } from "../../../common/local/dockerUtils.js"; +import { LocalToolBase } from "../localTool.js"; +import { OperationType } from "../../tool.js"; + +export class InspectClusterTool extends LocalToolBase { + protected name = "local-inspect-cluster"; + protected description = "Inspect a specific local MongoDB cluster"; + protected operationType: OperationType = "read"; + protected argsShape = { + clusterName: z.string().nonempty("Cluster name is required"), + }; + + protected async execute({ clusterName }: { clusterName: string }): Promise { + const cluster = await getValidatedClusterDetails(clusterName); + return this.formatClusterDetails(cluster); + } + + private formatClusterDetails(cluster: ClusterDetails): CallToolResult { + const connectionString = + cluster.port.toLowerCase() === "unknown" + ? null + : `mongodb://localhost:${cluster.port}/?directConnection=true`; + + const connectionStringText = connectionString + ? `Connection String: ${connectionString}` + : "Connection String: Not available"; + + return { + content: [ + { + type: "text", + text: `Details for cluster "${cluster.name}":\n +Cluster Name: ${cluster.name} +MongoDB Version: ${cluster.mongodbVersion} +Status: ${cluster.status} +Health: ${cluster.health} +Port: ${cluster.port} +DBUser: ${cluster.dbUser} +${connectionStringText}`, + }, + ], + }; + } +} diff --git a/src/tools/local/read/listClusters.ts b/src/tools/local/read/listClusters.ts new file mode 100644 index 0000000..42d81b3 --- /dev/null +++ b/src/tools/local/read/listClusters.ts @@ -0,0 +1,87 @@ +import { exec } from "child_process"; +import { promisify } from "util"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { ClusterDetails } from "../../../common/local/dockerUtils.js"; +import { LocalToolBase, DockerPsSummary } from "../localTool.js"; +import { OperationType } from "../../tool.js"; + +export class ListClustersTool extends LocalToolBase { + protected name = "local-list-clusters"; + protected description = "List local MongoDB clusters"; + protected operationType: OperationType = "read"; + protected argsShape = {}; + + protected async execute(): Promise { + const clusters = await this.getLocalMongoDBClusters(); + return this.formatClustersTable(clusters); + } + + private async getLocalMongoDBClusters(): Promise { + const execAsync = promisify(exec); + // List all containers + const { stdout } = await execAsync("docker ps -a --format '{{json .}}'"); + const lines = stdout.trim().split("\n"); + + const clusters: ClusterDetails[] = []; + + for (const line of lines) { + try { + const container = JSON.parse(line) as DockerPsSummary; + if (!container.Image.startsWith("mongodb")) { + continue; // Skip non-MongoDB containers + } + + // Extract MongoDB version from the Image field + const mongodbVersionMatch = container.Image.match(/mongodb\/mongodb-atlas-local:(.+)/); + const mongodbVersion = mongodbVersionMatch ? mongodbVersionMatch[1] : "Unknown"; + + // Extract port (if available) + const portMatch = container.Ports?.match(/(?:\d{1,3}\.){3}\d{1,3}:(\d+)->27017\/tcp/); + const port = portMatch ? portMatch[1] : null; + + // Extract health status using docker inspect + const { stdout: statusOutput } = await execAsync( + `docker inspect -f '{{.State.Status}}' ${container.Names}` + ); + const healthStatus = statusOutput.trim(); + + const cluster: ClusterDetails = { + name: container.Names, + mongodbVersion, + status: healthStatus, + health: "", + port: port ? port : "Unknown", + dbUser: "", + isAuth: false, + }; + + clusters.push(cluster); + } catch { + console.warn(`Failed to parse line: ${line}`); + } + } + + return clusters; + } + + private formatClustersTable(clusters: ClusterDetails[]): CallToolResult { + const rows = clusters + .map((c) => `${c.name.padEnd(15)}| ${c.status.padEnd(20)}| ${c.mongodbVersion.padEnd(20)}| ${c.port}`) + .join("\n"); + + return { + content: [ + { + type: "text", + text: `Here are your local MongoDB clusters:\n${rows}`, + }, + { + type: "text", + text: `Cluster Name | Status | MongoDB Version | Port +----------------|----------------|----------------|---------------- +${rows}`, + }, + ], + }; + } +} diff --git a/src/tools/local/tools.ts b/src/tools/local/tools.ts new file mode 100644 index 0000000..6aa627f --- /dev/null +++ b/src/tools/local/tools.ts @@ -0,0 +1,13 @@ +import { ConnectClusterTool } from "./metadata/connectCluster.js"; +import { CreateClusterTool } from "./create/createCluster.js"; +import { DeleteClusterTool } from "./delete/deleteCluster.js"; +import { InspectClusterTool } from "./read/inspectCluster.js"; +import { ListClustersTool } from "./read/listClusters.js"; + +export const LocalTools = [ + ConnectClusterTool, + CreateClusterTool, + DeleteClusterTool, + InspectClusterTool, + ListClustersTool, +]; diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 5e4fc1a..5ae3780 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -10,7 +10,7 @@ import { UserConfig } from "../config.js"; export type ToolArgs = z.objectOutputType; export type OperationType = "metadata" | "read" | "create" | "delete" | "update"; -export type ToolCategory = "mongodb" | "atlas"; +export type ToolCategory = "mongodb" | "atlas" | "local"; export type TelemetryToolMetadata = { projectId?: string; orgId?: string;