diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index ff50e14b..5a99be15 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -1,35 +1,46 @@ --- name: Code Health on: - push: - branches: - - main - pull_request: + push: + branches: + - main + pull_request: jobs: - check-style: - runs-on: ubuntu-latest - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - cache: "npm" - - name: Install dependencies - run: npm ci - - name: Run style check - run: npm run check + check-style: + runs-on: ubuntu-latest + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run style check + run: npm run check - run-tests: - runs-on: ubuntu-latest - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - cache: "npm" - - name: Install dependencies - run: npm ci - - name: Run tests - run: npm test + run-tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + if: matrix.os != 'windows-latest' + - name: Install keyring deps on Ubuntu + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt update -y + sudo apt install -y gnome-keyring libdbus-1-dev + + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 02a99122..45fb0726 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,29 +1,29 @@ --- name: Publish on: - push: - tags: - - v* + push: + tags: + - v* jobs: - publish: - runs-on: ubuntu-latest - environment: Production - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - registry-url: "https://registry.npmjs.org" - cache: "npm" - - name: Build package - run: | - npm ci - npm run build - - name: Publish to NPM - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish Github release - run: | - gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes + publish: + runs-on: ubuntu-latest + environment: Production + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + registry-url: "https://registry.npmjs.org" + cache: "npm" + - name: Build package + run: | + npm ci + npm run build + - name: Publish to NPM + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Github release + run: | + gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes diff --git a/.gitignore b/.gitignore index 26c2b9c7..ec17480c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ node_modules # Sensitive state.json + +tests/tmp diff --git a/.prettierignore b/.prettierignore index 9eb4595e..ffb0dcd0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ dist coverage package-lock.json +tests/tmp diff --git a/.prettierrc.json b/.prettierrc.json index b52f3251..076027c3 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -18,6 +18,13 @@ "tabWidth": 2, "printWidth": 80 } + }, + { + "files": "*.yaml", + "options": { + "tabWidth": 2, + "printWidth": 80 + } } ] } diff --git a/package-lock.json b/package-lock.json index 536fdd2e..27ab6a0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", + "mongodb-runner": "^5.8.2", "openapi-types": "^12.1.3", "openapi-typescript": "^7.6.1", "prettier": "^3.5.3", @@ -3117,6 +3118,66 @@ "system-ca": "^2.0.1" } }, + "node_modules/@mongodb-js/mongodb-downloader": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/mongodb-downloader/-/mongodb-downloader-0.3.9.tgz", + "integrity": "sha512-6lEIESINiIAeQUw95+hkfxG6129r6KiPU2TNOcxb30PsGgFHPJFg7QY8UoSQXjDE9YaENlr6oQm3c1XDixWeEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "decompress": "^4.2.1", + "mongodb-download-url": "^1.5.7", + "node-fetch": "^2.7.0", + "tar": "^6.1.15" + } + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mongodb-js/mongodb-downloader/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@mongodb-js/oidc-http-server-pages": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongodb-js/oidc-http-server-pages/-/oidc-http-server-pages-1.1.4.tgz", @@ -6806,6 +6867,7 @@ "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", @@ -6820,8 +6882,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/basic-ftp": { "version": "5.0.5", @@ -7037,6 +7098,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -7052,12 +7114,46 @@ } ], "license": "MIT", - "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7429,6 +7525,13 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7555,6 +7658,13 @@ "url": "https://opencollective.com/core-js" } }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -7687,6 +7797,26 @@ "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==", "dev": true }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -7703,6 +7833,219 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-tar/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -7980,8 +8323,8 @@ "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", - "optional": true, "dependencies": { "once": "^1.4.0" } @@ -8586,6 +8929,16 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -8622,6 +8975,16 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -8812,8 +9175,34 @@ "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==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -9222,6 +9611,7 @@ "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", @@ -9236,8 +9626,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -9475,6 +9864,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9531,6 +9927,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10882,6 +11285,56 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -11039,6 +11492,68 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongodb-download-url": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/mongodb-download-url/-/mongodb-download-url-1.5.7.tgz", + "integrity": "sha512-GpQJAfYmfYwqVXUyfIUQXe5kFoiCK5kORBJnPixAmQGmabZix6fBTpX7vSy3J46VgiAe+VEOjSikK/TcGKTw+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "minimist": "^1.2.8", + "node-fetch": "^2.7.0", + "semver": "^7.7.1" + }, + "bin": { + "mongodb-download-url": "bin/mongodb-download-url.js" + } + }, + "node_modules/mongodb-download-url/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/mongodb-download-url/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mongodb-download-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/mongodb-download-url/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/mongodb-log-writer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-2.4.1.tgz", @@ -11064,6 +11579,24 @@ "integrity": "sha512-L4L3byUH/V/L6YH954NBM/zJpyDHQYmm9eUCxMxqMUfiYCPtmCK1sv/LhxE7UonOkFNEAT6eq2J8gIWGUpHcJA==", "license": "Apache-2.0" }, + "node_modules/mongodb-runner": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.8.2.tgz", + "integrity": "sha512-Fsr87S3P75jAd/D1ly0/lODpjtFpHd+q9Ml2KjQQmPeGisdjCDO9bFOJ1F9xrdtvww2eeR8xKBXrtZYdP5P59A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/mongodb-downloader": "^0.3.9", + "@mongodb-js/saslprep": "^1.2.2", + "debug": "^4.4.0", + "mongodb": "^6.9.0", + "mongodb-connection-string-url": "^3.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "mongodb-runner": "bin/runner.js" + } + }, "node_modules/mongodb-schema": { "version": "12.6.2", "resolved": "https://registry.npmjs.org/mongodb-schema/-/mongodb-schema-12.6.2.tgz", @@ -11834,6 +12367,13 @@ "node": ">=16" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-scrollbar": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", @@ -11861,6 +12401,39 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -12100,6 +12673,13 @@ "node": ">=6" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -12848,6 +13428,20 @@ "loose-envify": "^1.1.0" } }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -13529,6 +14123,16 @@ "node": ">=8" } }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -13758,6 +14362,24 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", @@ -13788,6 +14410,16 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13803,6 +14435,13 @@ "node": ">=8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -13822,6 +14461,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14127,6 +14773,17 @@ "node": ">=0.8.0" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici": { "version": "6.21.2", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", @@ -14498,6 +15155,16 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14562,6 +15229,17 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index dae56b98..b91e20f6 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-node": "^29.7.0", + "mongodb-runner": "^5.8.2", "openapi-types": "^12.1.3", "openapi-typescript": "^7.6.1", "prettier": "^3.5.3", diff --git a/src/server.ts b/src/server.ts index 1df18e66..bf4ca16f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,6 +12,7 @@ export class Server { state: State = defaultState; apiClient?: ApiClient; initialized: boolean = false; + private server?: McpServer; private async init() { if (this.initialized) { @@ -32,7 +33,8 @@ export class Server { this.initialized = true; } - private createMcpServer(): McpServer { + async connect(transport: Transport) { + await this.init(); const server = new McpServer({ name: "MongoDB Atlas", version: config.version, @@ -43,15 +45,19 @@ export class Server { registerAtlasTools(server, this.state, this.apiClient); registerMongoDBTools(server, this.state); - return server; - } - - async connect(transport: Transport) { - await this.init(); - const server = this.createMcpServer(); await server.connect(transport); await initializeLogger(server); + this.server = server; logger.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`); } + + async close(): Promise { + try { + await this.state.serviceProvider?.close(true); + } catch { + // Ignore errors during service provider close + } + await this.server?.close(); + } } diff --git a/src/state.ts b/src/state.ts index f99f3cb6..bdd167b0 100644 --- a/src/state.ts +++ b/src/state.ts @@ -13,20 +13,21 @@ export class State { serviceProvider?: NodeDriverServiceProvider; public async persistCredentials(): Promise { - await this.entry.setPassword(JSON.stringify(this.credentials)); + try { + await this.entry.setPassword(JSON.stringify(this.credentials)); + } catch (err) { + logger.error(mongoLogId(1_000_008), "state", `Failed to save state: ${err}`); + } } - public async loadCredentials(): Promise { + public async loadCredentials(): Promise { try { const data = await this.entry.getPassword(); if (data) { this.credentials = JSON.parse(data); } - - return true; } catch (err: unknown) { logger.error(mongoLogId(1_000_007), "state", `Failed to load state: ${err}`); - return false; } } } diff --git a/src/tools/mongodb/mongodbTool.ts b/src/tools/mongodb/mongodbTool.ts index cc376aea..fe9f3813 100644 --- a/src/tools/mongodb/mongodbTool.ts +++ b/src/tools/mongodb/mongodbTool.ts @@ -57,7 +57,9 @@ export abstract class MongoDBToolBase extends ToolBase { const provider = await NodeDriverServiceProvider.connect(connectionString, { productDocsLink: "https://docs.mongodb.com/todo-mcp", productName: "MongoDB MCP", - readConcern: config.connectOptions.readConcern, + readConcern: { + level: config.connectOptions.readConcern, + }, readPreference: config.connectOptions.readPreference, writeConcern: { w: config.connectOptions.writeConcern, diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts new file mode 100644 index 00000000..d20da21a --- /dev/null +++ b/tests/integration/helpers.ts @@ -0,0 +1,88 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "./inMemoryTransport.js"; +import { Server } from "../../src/server.js"; +import runner, { MongoCluster } from "mongodb-runner"; +import path from "path"; +import fs from "fs/promises"; +import defaultState from "../../src/state.js"; + +export async function setupIntegrationTest({ mockStateStore = true }: { mockStateStore?: boolean } = {}): Promise<{ + client: Client; + server: Server; + teardown: () => Promise; +}> { + let loadCredentialsMock: jest.SpyInstance | undefined; + let saveCredentialsMock: jest.SpyInstance | undefined; + if (mockStateStore) { + // Mock the load/persist credentials method to avoid state loading/restore messing up with the tests + loadCredentialsMock = jest.spyOn(defaultState, "loadCredentials").mockImplementation(() => Promise.resolve()); + saveCredentialsMock = jest + .spyOn(defaultState, "persistCredentials") + .mockImplementation(() => Promise.resolve()); + } + + const clientTransport = new InMemoryTransport(); + const serverTransport = new InMemoryTransport(); + + await serverTransport.start(); + await clientTransport.start(); + + clientTransport.output.pipeTo(serverTransport.input); + serverTransport.output.pipeTo(clientTransport.input); + + const client = new Client( + { + name: "test-client", + version: "1.2.3", + }, + { + capabilities: {}, + } + ); + + const server = new Server(); + await server.connect(serverTransport); + await client.connect(clientTransport); + + return { + client, + server, + teardown: async () => { + await client.close(); + await server.close(); + + loadCredentialsMock?.mockRestore(); + saveCredentialsMock?.mockRestore(); + }, + }; +} + +export async function runMongoDB(): Promise { + const tmpDir = path.join(__dirname, "..", "tmp"); + await fs.mkdir(tmpDir, { recursive: true }); + + try { + const cluster = await MongoCluster.start({ + tmpDir: path.join(tmpDir, "mongodb-runner", "dbs"), + logDir: path.join(tmpDir, "mongodb-runner", "logs"), + topology: "standalone", + }); + + return cluster; + } catch (err) { + throw err; + } +} + +export function validateToolResponse(content: unknown): string { + expect(Array.isArray(content)).toBe(true); + + const response = content as Array<{ type: string; text: string }>; + for (const item of response) { + expect(item).toHaveProperty("type"); + expect(item).toHaveProperty("text"); + expect(item.type).toBe("text"); + } + + return response.map((item) => item.text).join("\n"); +} diff --git a/tests/integration/inMemoryTransport.ts b/tests/integration/inMemoryTransport.ts new file mode 100644 index 00000000..a12c4625 --- /dev/null +++ b/tests/integration/inMemoryTransport.ts @@ -0,0 +1,58 @@ +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; + +export class InMemoryTransport implements Transport { + private outputController: ReadableStreamDefaultController; + + private startPromise: Promise; + + public output: ReadableStream; + public input: WritableStream; + + constructor() { + const [inputReady, inputResolve] = InMemoryTransport.getPromise(); + const [outputReady, outputResolve] = InMemoryTransport.getPromise(); + + this.output = new ReadableStream({ + start: (controller) => { + this.outputController = controller; + outputResolve(); + }, + }); + + this.input = new WritableStream({ + write: (message) => this.onmessage?.(message), + start: () => { + inputResolve(); + }, + }); + + this.startPromise = Promise.all([inputReady, outputReady]); + } + + async start(): Promise { + await this.startPromise; + } + + send(message: JSONRPCMessage): Promise { + this.outputController.enqueue(message); + return Promise.resolve(); + } + + async close(): Promise { + this.outputController.close(); + this.onclose?.(); + } + onclose?: (() => void) | undefined; + onerror?: ((error: Error) => void) | undefined; + onmessage?: ((message: JSONRPCMessage) => void) | undefined; + sessionId?: string | undefined; + + private static getPromise(): [Promise, resolve: () => void] { + let resolve: () => void; + const promise = new Promise((res) => { + resolve = res; + }); + return [promise, resolve!]; + } +} diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts new file mode 100644 index 00000000..7b9d1b46 --- /dev/null +++ b/tests/integration/server.test.ts @@ -0,0 +1,47 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { Server } from "../../src/server.js"; +import { setupIntegrationTest } from "./helpers.js"; + +describe("Server integration test", () => { + let client: Client; + let server: Server; + + beforeEach(async () => { + ({ client, server } = await setupIntegrationTest()); + }); + + afterEach(async () => { + await client?.close(); + await server?.close(); + }); + + describe("list capabilities", () => { + it("should return positive number of tools", async () => { + const tools = await client.listTools(); + expect(tools).toBeDefined(); + expect(tools.tools.length).toBeGreaterThan(0); + }); + + it("should return no resources", async () => { + await expect(() => client.listResources()).rejects.toMatchObject({ + message: "MCP error -32601: Method not found", + }); + }); + + it("should return no prompts", async () => { + await expect(() => client.listPrompts()).rejects.toMatchObject({ + message: "MCP error -32601: Method not found", + }); + }); + + it("should return capabilities", async () => { + const capabilities = client.getServerCapabilities(); + expect(capabilities).toBeDefined(); + expect(capabilities?.completions).toBeUndefined(); + expect(capabilities?.experimental).toBeUndefined(); + expect(capabilities?.tools).toBeDefined(); + expect(capabilities?.logging).toBeDefined(); + expect(capabilities?.prompts).toBeUndefined(); + }); + }); +}); diff --git a/tests/integration/tools/mongodb/connect.test.ts b/tests/integration/tools/mongodb/connect.test.ts new file mode 100644 index 00000000..d304c266 --- /dev/null +++ b/tests/integration/tools/mongodb/connect.test.ts @@ -0,0 +1,109 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { runMongoDB, setupIntegrationTest, validateToolResponse } from "../../helpers.js"; +import runner from "mongodb-runner"; + +import config from "../../../../src/config.js"; + +describe("Connect tool", () => { + let client: Client; + let serverClientTeardown: () => Promise; + + let cluster: runner.MongoCluster; + + beforeAll(async () => { + cluster = await runMongoDB(); + }, 60_000); + + afterEach(async () => { + await serverClientTeardown?.(); + }); + + afterAll(async () => { + await cluster.close(); + }); + + describe("with default config", () => { + beforeEach(async () => { + ({ client, teardown: serverClientTeardown } = await setupIntegrationTest()); + }); + + it("should have correct metadata", async () => { + const tools = await client.listTools(); + const connectTool = tools.tools.find((tool) => tool.name === "connect"); + expect(connectTool).toBeDefined(); + expect(connectTool!.description).toBe("Connect to a MongoDB instance"); + expect(connectTool!.inputSchema.type).toBe("object"); + expect(connectTool!.inputSchema.properties).toBeDefined(); + + const propertyNames = Object.keys(connectTool!.inputSchema.properties!); + expect(propertyNames).toHaveLength(1); + expect(propertyNames[0]).toBe("connectionStringOrClusterName"); + + const connectionStringOrClusterNameProp = connectTool!.inputSchema.properties![propertyNames[0]] as { + type: string; + description: string; + }; + expect(connectionStringOrClusterNameProp.type).toBe("string"); + expect(connectionStringOrClusterNameProp.description).toContain("MongoDB connection string"); + expect(connectionStringOrClusterNameProp.description).toContain("cluster name"); + }); + + describe("without connection string", () => { + it("prompts for connection string", async () => { + const response = await client.callTool({ name: "connect", arguments: {} }); + const content = validateToolResponse(response.content); + expect(content).toContain("No connection details provided"); + expect(content).toContain("mongodb://localhost:27017"); + }); + }); + + describe("with connection string", () => { + it("connects to the database", async () => { + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: cluster.connectionString }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(cluster.connectionString); + }); + }); + + describe("with invalid connection string", () => { + it("returns error message", async () => { + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Error running connect"); + }); + }); + }); + + describe("with connection string in config", () => { + beforeEach(async () => { + config.connectionString = cluster.connectionString; + + ({ client, teardown: serverClientTeardown } = await setupIntegrationTest()); + }); + + it("uses the connection string from config", async () => { + const response = await client.callTool({ name: "connect", arguments: {} }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(cluster.connectionString); + }); + + it("prefers connection string from arguments", async () => { + const newConnectionString = `${cluster.connectionString}?appName=foo-bar`; + const response = await client.callTool({ + name: "connect", + arguments: { connectionStringOrClusterName: newConnectionString }, + }); + const content = validateToolResponse(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(newConnectionString); + }); + }); +}); diff --git a/tests/unit/index.test.ts b/tests/unit/index.test.ts index 8773fd75..2e307bfb 100644 --- a/tests/unit/index.test.ts +++ b/tests/unit/index.test.ts @@ -1,6 +1,6 @@ import { describe, it } from "@jest/globals"; import { runServer } from "../../src/index"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // mock the StdioServerTransport jest.mock("@modelcontextprotocol/sdk/server/stdio");