From 3118f19fa7264e767795d1dd947fc0bca7878b07 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 13:55:05 +0100 Subject: [PATCH 1/7] ci: publish to npm --- .github/workflows/code_health.yaml | 31 ---- .github/workflows/publish.yaml | 26 +++ .gitignore | 1 + dist/client.js | 225 ------------------------- dist/config.js | 13 -- dist/index.js | 12 -- dist/server.js | 254 ----------------------------- dist/state.js | 33 ---- package.json | 1 + 9 files changed, 28 insertions(+), 568 deletions(-) create mode 100644 .github/workflows/publish.yaml delete mode 100644 dist/client.js delete mode 100644 dist/config.js delete mode 100755 dist/index.js delete mode 100644 dist/server.js delete mode 100644 dist/state.js diff --git a/.github/workflows/code_health.yaml b/.github/workflows/code_health.yaml index e080a268..147ce8c8 100644 --- a/.github/workflows/code_health.yaml +++ b/.github/workflows/code_health.yaml @@ -6,37 +6,6 @@ on: - main pull_request: jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - with: - config: ${{ vars.PERMISSIONS_CONFIG }} - - name: Checkout repository - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - cache: "npm" - - name: Install dependencies - run: | - npm ci - - name: build - run: | - npm run build - - name: Check for uncommitted files - run: | - export FILES= - FILES=$(git ls-files -o -m --directory --exclude-standard --no-empty-directory) - export LINES= - LINES=$(echo "$FILES" | awk 'NF' | wc -l) - if [ "$LINES" -ne 0 ]; then - echo "Detected files that need to be committed:" - echo "${FILES}" - echo "" - echo "Try running: npm run build" - exit 1 - fi prettier: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..350a2a3f --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,26 @@ +--- +name: Publish +on: + push: + tags: + - v* +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 + with: + config: ${{ vars.PERMISSIONS_CONFIG }} + - name: Checkout Repository + uses: actions/checkout@v2 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: "npm" + - name: Install dependencies + run: | + npm ci + - run: | + npm publish + - run: | + gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes \ No newline at end of file diff --git a/.gitignore b/.gitignore index ffe0c5f9..26c2b9c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +dist node_modules .vscode/mcp.json .github/prompts/* diff --git a/dist/client.js b/dist/client.js deleted file mode 100644 index 96940353..00000000 --- a/dist/client.js +++ /dev/null @@ -1,225 +0,0 @@ -import config from "./config.js"; -export class ApiClientError extends Error { - constructor(message, response = undefined) { - super(message); - this.name = "ApiClientError"; - this.response = response; - } -} -export class ApiClient { - constructor(options) { - const { token, saveToken } = options; - this.token = token; - this.saveToken = saveToken; - } - defaultOptions() { - const authHeaders = !this.token?.access_token - ? null - : { - Authorization: `Bearer ${this.token.access_token}`, - }; - return { - method: "GET", - credentials: !this.token?.access_token ? undefined : "include", - headers: { - "Content-Type": "application/json", - Accept: "application/vnd.atlas.2025-04-07+json", - "User-Agent": config.userAgent, - ...authHeaders, - }, - }; - } - async storeToken(token) { - this.token = token; - if (this.saveToken) { - await this.saveToken(token); - } - return token; - } - async do(endpoint, options) { - if (!this.token || !this.token.access_token) { - throw new Error("Not authenticated. Please run the auth tool first."); - } - const url = new URL(`api/atlas/v2${endpoint}`, `${config.apiBaseURL}`); - if (!this.checkTokenExpiry()) { - await this.refreshToken(); - } - const defaultOpt = this.defaultOptions(); - const opt = { - ...defaultOpt, - ...options, - headers: { - ...defaultOpt.headers, - ...options?.headers, - }, - }; - const response = await fetch(url, opt); - if (!response.ok) { - throw new ApiClientError(`Error calling Atlas API: ${response.statusText}`, response); - } - return (await response.json()); - } - async authenticate() { - const endpoint = "api/private/unauth/account/device/authorize"; - const authUrl = new URL(endpoint, config.apiBaseURL); - const response = await fetch(authUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - }, - body: new URLSearchParams({ - client_id: config.clientID, - scope: "openid profile offline_access", - grant_type: "urn:ietf:params:oauth:grant-type:device_code", - }).toString(), - }); - if (!response.ok) { - throw new ApiClientError(`Failed to initiate authentication: ${response.statusText}`, response); - } - return (await response.json()); - } - async retrieveToken(device_code) { - const endpoint = "api/private/unauth/account/device/token"; - const url = new URL(endpoint, config.apiBaseURL); - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - client_id: config.clientID, - device_code: device_code, - grant_type: "urn:ietf:params:oauth:grant-type:device_code", - }).toString(), - }); - if (response.ok) { - const tokenData = await response.json(); - const buf = Buffer.from(tokenData.access_token.split(".")[1], "base64").toString(); - const jwt = JSON.parse(buf); - const expiry = new Date(jwt.exp * 1000); - return await this.storeToken({ ...tokenData, expiry }); - } - try { - const errorResponse = await response.json(); - if (errorResponse.errorCode === "DEVICE_AUTHORIZATION_PENDING") { - throw new ApiClientError("Authentication pending. Try again later.", response); - } - else if (errorResponse.error === "expired_token") { - throw new ApiClientError("Device code expired. Please restart the authentication process.", response); - } - else { - throw new ApiClientError("Device code expired. Please restart the authentication process.", response); - } - } - catch { - throw new ApiClientError("Failed to retrieve token. Please check your device code.", response); - } - } - async refreshToken(token) { - const endpoint = "api/private/unauth/account/device/token"; - const url = new URL(endpoint, config.apiBaseURL); - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - }, - body: new URLSearchParams({ - client_id: config.clientID, - refresh_token: (token || this.token)?.refresh_token || "", - grant_type: "refresh_token", - scope: "openid profile offline_access", - }).toString(), - }); - if (!response.ok) { - throw new ApiClientError(`Failed to refresh token: ${response.statusText}`, response); - } - const data = await response.json(); - const buf = Buffer.from(data.access_token.split(".")[1], "base64").toString(); - const jwt = JSON.parse(buf); - const expiry = new Date(jwt.exp * 1000); - const tokenToStore = { - ...data, - expiry, - }; - return await this.storeToken(tokenToStore); - } - async revokeToken(token) { - const endpoint = "api/private/unauth/account/device/token"; - const url = new URL(endpoint, config.apiBaseURL); - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - "User-Agent": config.userAgent, - }, - body: new URLSearchParams({ - client_id: config.clientID, - token: (token || this.token)?.access_token || "", - token_type_hint: "refresh_token", - }).toString(), - }); - if (!response.ok) { - throw new ApiClientError(`Failed to revoke token: ${response.statusText}`, response); - } - if (!token && this.token) { - this.token = undefined; - } - return; - } - checkTokenExpiry(token) { - try { - token = token || this.token; - if (!token || !token.access_token) { - return false; - } - if (!token.expiry) { - return false; - } - const expiryDelta = 10 * 1000; // 10 seconds in milliseconds - const expiryWithDelta = new Date(token.expiry.getTime() - expiryDelta); - return expiryWithDelta.getTime() > Date.now(); - } - catch { - return false; - } - } - async validateToken(token) { - if (this.checkTokenExpiry(token)) { - return true; - } - try { - await this.refreshToken(token); - return true; - } - catch { - return false; - } - } - /** - * Get all projects for the authenticated user - */ - async listProjects() { - return await this.do("/groups"); - } - /** - * Get a specific project by ID - */ - async getProject(projectId) { - return await this.do(`/groups/${projectId}`); - } - /** - * Get clusters for a specific project - */ - async listProjectClusters(projectId) { - return await this.do(`/groups/${projectId}/clusters`); - } - /** - * Get clusters for a specific project - */ - async listAllClusters() { - return await this.do(`/clusters`); - } -} diff --git a/dist/config.js b/dist/config.js deleted file mode 100644 index 40fedc8b..00000000 --- a/dist/config.js +++ /dev/null @@ -1,13 +0,0 @@ -import path from "path"; -import fs from "fs"; -const packageMetadata = fs.readFileSync(path.resolve("./package.json"), "utf8"); -const packageJson = JSON.parse(packageMetadata); -export const config = { - version: packageJson.version, - apiBaseURL: process.env.API_BASE_URL || "https://cloud.mongodb.com/", - clientID: process.env.CLIENT_ID || "0oabtxactgS3gHIR0297", - stateFile: process.env.STATE_FILE || path.resolve("./state.json"), - projectID: process.env.PROJECT_ID, - userAgent: `AtlasMCP/${packageJson.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`, -}; -export default config; diff --git a/dist/index.js b/dist/index.js deleted file mode 100755 index 868a3c8d..00000000 --- a/dist/index.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { Server } from "./server.js"; -async function runServer() { - const server = new Server(); - const transport = new StdioServerTransport(); - await server.connect(transport); -} -runServer().catch((error) => { - console.error(`Fatal error running server:`, error); - process.exit(1); -}); diff --git a/dist/server.js b/dist/server.js deleted file mode 100644 index 134b4c9d..00000000 --- a/dist/server.js +++ /dev/null @@ -1,254 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { ApiClient } from "./client.js"; -import { saveState, loadState } from "./state.js"; -import { config } from "./config.js"; -function log(level, message) { - console.error(`[${level.toUpperCase()}] ${message}`); -} -export class Server { - constructor() { - this.state = undefined; - this.apiClient = undefined; - this.initiated = false; - } - async init() { - if (this.initiated) { - return; - } - this.state = await loadState(); - this.apiClient = new ApiClient({ - token: this.state?.auth.token, - saveToken: (token) => { - if (!this.state) { - throw new Error("State is not initialized"); - } - this.state.auth.code = undefined; - this.state.auth.token = token; - this.state.auth.status = "issued"; - saveState(this.state); - }, - }); - this.initiated = true; - } - async ensureAuthenticated() { - switch (this.state.auth.status) { - case "not_auth": - return false; - case "requested": - try { - if (!this.state.auth.code) { - return false; - } - await this.apiClient.retrieveToken(this.state.auth.code.device_code); - return !!this.state.auth.token; - } - catch { - return false; - } - case "issued": - if (!this.state.auth.token) { - return false; - } - return await this.apiClient.validateToken(); - default: - throw new Error("Unknown authentication status"); - } - } - async authTool() { - const auth = await this.ensureAuthenticated(); - if (auth) { - log("INFO", "Already authenticated!"); - return { - content: [{ type: "text", text: "You are already authenticated!" }], - }; - } - try { - const code = await this.apiClient.authenticate(); - this.state.auth.status = "requested"; - this.state.auth.code = code; - this.state.auth.token = undefined; - await saveState(this.state); - return { - content: [ - { - type: "text", - text: `Please authenticate by visiting ${code.verification_uri} and entering the code ${code.user_code}`, - }, - ], - }; - } - catch (error) { - if (error instanceof Error) { - log("error", `Authentication error: ${error}`); - return { - content: [{ type: "text", text: `Authentication failed: ${error.message}` }], - }; - } - else { - log("error", `Unknown authentication error: ${error}`); - return { - content: [{ type: "text", text: "Authentication failed due to an unknown error." }], - }; - } - } - } - async listClustersTool(projectId) { - try { - // Ensure user is authenticated or throw an error - const auth = await this.ensureAuthenticated(); - if (!auth) { - return { - content: [{ type: "text", text: "You need to be authenticated first" }], - }; - } - let clusters = undefined; - let introText = "Here are your MongoDB Atlas clusters:"; - const selectedProjectId = projectId || config.projectID; - if (!selectedProjectId) { - return { - content: [{ type: "text", text: "No project ID provided. Please specify a project ID." }], - }; - } - const project = await this.apiClient.getProject(selectedProjectId); - const data = await this.apiClient.listProjectClusters(project.id); - clusters = data.results || []; - try { - introText = `Here are the clusters in project "${project.name}" (${project.id}):`; - } - catch (e) { - log("error", `Error fetching project details: ${e}`); - } - if (clusters.length === 0) { - return { - content: [ - { - type: "text", - text: "No clusters found. You may need to create a cluster in your MongoDB Atlas account.", - }, - ], - }; - } - const formattedClusters = formatClustersTable(clusters); - return { - content: [ - { type: "text", text: introText }, - { type: "text", text: formattedClusters }, - ], - }; - } - catch (error) { - log("error", `Error listing clusters: ${error}`); - // If the error is authentication related, suggest using auth tool - if (error instanceof Error && error.message.includes("Not authenticated")) { - return { - content: [ - { type: "text", text: "You need to authenticate before listing clusters." }, - { type: "text", text: "Please use the 'auth' tool to log in to your MongoDB Atlas account." }, - ], - }; - } - return { - content: [ - { - type: "text", - text: `Error listing clusters: ${error instanceof Error ? error.message : String(error)}`, - }, - ], - }; - } - } - async listProjectsTool() { - try { - // Ensure user is authenticated or throw an error - const auth = await this.ensureAuthenticated(); - if (!auth) { - return { - content: [{ type: "text", text: "You need to be authenticated first" }], - }; - } - const projectsData = await this.apiClient.listProjects(); - const projects = projectsData.results || []; - if (!projects || projects.length === 0) { - return { - content: [{ type: "text", text: "No projects found in your MongoDB Atlas account." }], - }; - } - // Format projects as a table - const header = `Project Name | Project ID | Created At -----------------|----------------|----------------`; - const rows = projects - .map((project) => { - const createdAt = project.created ? new Date(project.created.$date).toLocaleString() : "N/A"; - return `${project.name} | ${project.id} | ${createdAt}`; - }) - .join("\n"); - const formattedProjects = `${header}\n${rows}`; - return { - content: [ - { type: "text", text: "Here are your MongoDB Atlas projects:" }, - { type: "text", text: formattedProjects }, - ], - }; - } - catch (error) { - log("error", `Error listing projects: ${error}`); - // If the error is authentication related, suggest using auth tool - if (error instanceof Error && error.message.includes("Not authenticated")) { - return { - content: [ - { type: "text", text: "You need to authenticate before listing projects." }, - { type: "text", text: "Please use the 'auth' tool to log in to your MongoDB Atlas account." }, - ], - }; - } - return { - content: [ - { - type: "text", - text: `Error listing projects: ${error instanceof Error ? error.message : String(error)}`, - }, - ], - }; - } - } - createMcpServer() { - const server = new McpServer({ - name: "MongoDB Atlas", - version: config.version, - }); - server.tool("auth", "Authenticate to Atlas", async () => this.authTool()); - let projectIdFilter = z - .string() - .describe("Optional Atlas project ID to filter clusters"); - if (config.projectID) { - projectIdFilter = projectIdFilter.optional(); - } - server.tool("list-clusters", "Lists MongoDB Atlas clusters", { - projectId: projectIdFilter, - }, async ({ projectId }) => this.listClustersTool(projectId)); - server.tool("list-projects", "Lists MongoDB Atlas projects", async () => this.listProjectsTool()); - return server; - } - async connect(transport) { - await this.init(); - const server = this.createMcpServer(); - await server.connect(transport); - } -} -function formatClustersTable(clusters) { - if (clusters.length === 0) { - return "No clusters found."; - } - const header = `Cluster Name | State | MongoDB Version | Region | Connection String -----------------|----------------|----------------|----------------|----------------|----------------`; - const rows = clusters - .map((cluster) => { - const region = cluster.providerSettings?.regionName || "N/A"; - const connectionString = cluster.connectionStrings?.standard || "N/A"; - const mongoDBVersion = cluster.mongoDBVersion || "N/A"; - return `${cluster.name} | ${cluster.stateName} | ${mongoDBVersion} | ${region} | ${connectionString}`; - }) - .join("\n"); - return `${header}\n${rows}`; -} diff --git a/dist/state.js b/dist/state.js deleted file mode 100644 index 440cb98f..00000000 --- a/dist/state.js +++ /dev/null @@ -1,33 +0,0 @@ -import fs from "fs"; -import config from "./config.js"; -export async function saveState(state) { - return new Promise((resolve, reject) => { - fs.writeFile(config.stateFile, JSON.stringify(state), function (err) { - if (err) { - return reject(err); - } - return resolve(); - }); - }); -} -export async function loadState() { - return new Promise((resolve, reject) => { - fs.readFile(config.stateFile, "utf-8", (err, data) => { - if (err) { - if (err.code === "ENOENT") { - // File does not exist, return default state - const defaultState = { - auth: { - status: "not_auth", - }, - }; - return resolve(defaultState); - } - else { - return reject(err); - } - } - return resolve(JSON.parse(data)); - }); - }); -} diff --git a/package.json b/package.json index 75d52009..01f0c6b2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "dist/index.js", "type": "module", "scripts": { + "prepare": "npm run build", "build:clean": "rm -rf dist", "build:compile": "npx tsc", "build:addshebang": "echo '#!/usr/bin/env node' > dist/index2.js && cat dist/index.js >> dist/index2.js && mv dist/index2.js dist/index.js", From c6fb9b66423978931586395c22ed46f9b77045de Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:12:21 +0100 Subject: [PATCH 2/7] fix: format --- .github/workflows/publish.yaml | 2 +- package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 350a2a3f..0c3962da 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -23,4 +23,4 @@ jobs: - run: | npm publish - run: | - gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes \ No newline at end of file + gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes diff --git a/package.json b/package.json index 01f0c6b2..42d164e4 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "inspect": "npm run build && npx @modelcontextprotocol/inspector -- dist/index.js", "prettier": "prettier", "check": "npm run check:lint && npm run check:format", - "check:lint": "npx eslint .", - "check:format": "npx prettier -c .", - "reformat": "npx prettier -- --write ." + "check:lint": "eslint .", + "check:format": "prettier -c .", + "reformat": "npx prettier --write ." }, "license": "Apache-2.0", "devDependencies": { From b1f9a447909ea2007a8319234c61ade1f44baf3e Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:21:07 +0100 Subject: [PATCH 3/7] fix: add secrets --- .github/workflows/publish.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 0c3962da..72c857bf 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -7,6 +7,7 @@ on: jobs: publish: runs-on: ubuntu-latest + environment: Production steps: - uses: GitHubSecurityLab/actions-permissions/monitor@v1 with: @@ -16,11 +17,14 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: package.json + registry-url: 'https://registry.npmjs.org' cache: "npm" - name: Install dependencies run: | npm ci - - run: | + - env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | npm publish - run: | gh release create ${{ github.ref }} --title "${{ github.ref }}" --notes "Release ${{ github.ref }}" --generate-notes From de622e827e31b6bce92db1ba57018a3dfcec2177 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:22:45 +0100 Subject: [PATCH 4/7] chore: address comments --- .github/workflows/publish.yaml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 72c857bf..4497765e 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: package.json - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" cache: "npm" - name: Install dependencies run: | diff --git a/package.json b/package.json index 42d164e4..8c5d566e 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "check": "npm run check:lint && npm run check:format", "check:lint": "eslint .", "check:format": "prettier -c .", - "reformat": "npx prettier --write ." + "reformat": "prettier --write ." }, "license": "Apache-2.0", "devDependencies": { From 9837b4fdfdd0fbecaf5a3466c6b6e5297dfc30ad Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:23:29 +0100 Subject: [PATCH 5/7] chore: address comments --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c5d566e..8504b7a3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "prepare": "npm run build", "build:clean": "rm -rf dist", - "build:compile": "npx tsc", + "build:compile": "tsc", "build:addshebang": "echo '#!/usr/bin/env node' > dist/index2.js && cat dist/index.js >> dist/index2.js && mv dist/index2.js dist/index.js", "build:chmod": "chmod +x dist/index.js", "build": "npm run build:clean && npm run build:compile && npm run build:addshebang && npm run build:chmod", From 3be683b995ab129ab6600cbdcfb7e23a361c0ae3 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:25:05 +0100 Subject: [PATCH 6/7] chore: drive-by --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8825ce68..ebfb6e03 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ It should look like this ```json { "servers": { - "demo-atlas-server": { + "mongodb-mcp-server": { "type": "stdio", "command": "/Users//workplace/atlas-mcp-server/dist/index.js", "args": [] From c57d412d5657a643007ed2793d4a9487a4f3de94 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Tue, 8 Apr 2025 14:34:02 +0100 Subject: [PATCH 7/7] fix: remove private from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8504b7a3..d1d369c8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "mongodb-mcp-server", "version": "0.0.0", - "private": true, "main": "dist/index.js", "type": "module", "scripts": {