From 1407270cba38a16b287b75dacde4c052af99600f Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 14:49:19 +0200 Subject: [PATCH 1/5] add a mechanism to disable tools from the config --- src/config.ts | 12 +++++- src/tools/atlas/atlasTool.ts | 4 +- src/tools/atlas/createAccessList.ts | 3 +- src/tools/atlas/createDBUser.ts | 3 +- src/tools/atlas/createFreeCluster.ts | 3 +- src/tools/atlas/inspectAccessList.ts | 3 +- src/tools/atlas/inspectCluster.ts | 3 +- src/tools/atlas/listClusters.ts | 3 +- src/tools/atlas/listDBUsers.ts | 3 +- src/tools/atlas/listProjects.ts | 2 + src/tools/mongodb/collectionIndexes.ts | 6 +-- src/tools/mongodb/connect.ts | 6 +-- src/tools/mongodb/create/insertMany.ts | 6 +-- src/tools/mongodb/create/insertOne.ts | 6 +-- src/tools/mongodb/createIndex.ts | 6 +-- src/tools/mongodb/delete/deleteMany.ts | 6 +-- src/tools/mongodb/delete/deleteOne.ts | 6 +-- src/tools/mongodb/delete/dropCollection.ts | 6 +-- src/tools/mongodb/delete/dropDatabase.ts | 6 +-- .../mongodb/metadata/collectionSchema.ts | 6 +-- .../mongodb/metadata/collectionStorageSize.ts | 6 +-- src/tools/mongodb/metadata/dbStats.ts | 6 +-- src/tools/mongodb/metadata/listCollections.ts | 6 +-- src/tools/mongodb/metadata/listDatabases.ts | 5 ++- src/tools/mongodb/mongodbTool.ts | 6 +-- src/tools/mongodb/read/aggregate.ts | 6 +-- src/tools/mongodb/read/count.ts | 6 +-- src/tools/mongodb/read/find.ts | 6 +-- src/tools/mongodb/update/renameCollection.ts | 6 +-- src/tools/mongodb/update/updateMany.ts | 6 +-- src/tools/mongodb/update/updateOne.ts | 6 +-- src/tools/tool.ts | 42 +++++++++++++++++++ 32 files changed, 134 insertions(+), 72 deletions(-) diff --git a/src/config.ts b/src/config.ts index ecdf32ad..21db58b6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,6 +19,7 @@ interface UserConfig { writeConcern: W; timeoutMS: number; }; + disabledTools: Array; } const defaults: UserConfig = { @@ -29,6 +30,7 @@ const defaults: UserConfig = { writeConcern: "majority", timeoutMS: 30_000, }, + disabledTools: [], }; const mergedUserConfig = { @@ -74,6 +76,12 @@ function getEnvConfig(): Partial { return; } + // Try to parse an array of values + if (value.indexOf(",") !== -1) { + obj[currentField] = value.split(",").map((v) => v.trim()); + return; + } + obj[currentField] = value; return; } @@ -107,5 +115,7 @@ function SNAKE_CASE_toCamelCase(str: string): string { // Reads the cli args and parses them into a UserConfig object. function getCliConfig() { - return argv(process.argv.slice(2)) as unknown as Partial; + return argv(process.argv.slice(2), { + array: ["disabledTools"], + }) as unknown as Partial; } diff --git a/src/tools/atlas/atlasTool.ts b/src/tools/atlas/atlasTool.ts index 4aef681c..1e9dd313 100644 --- a/src/tools/atlas/atlasTool.ts +++ b/src/tools/atlas/atlasTool.ts @@ -1,8 +1,10 @@ -import { ToolBase } from "../tool.js"; +import { ToolBase, ToolCategory } from "../tool.js"; import { State } from "../../state.js"; export abstract class AtlasToolBase extends ToolBase { constructor(state: State) { super(state); } + + protected category: ToolCategory = "atlas"; } diff --git a/src/tools/atlas/createAccessList.ts b/src/tools/atlas/createAccessList.ts index 09a991c8..270051f2 100644 --- a/src/tools/atlas/createAccessList.ts +++ b/src/tools/atlas/createAccessList.ts @@ -1,13 +1,14 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; const DEFAULT_COMMENT = "Added by Atlas MCP"; export class CreateAccessListTool extends AtlasToolBase { protected name = "atlas-create-access-list"; protected description = "Allow Ip/CIDR ranges to access your MongoDB Atlas clusters."; + protected operationType: OperationType = "create"; protected argsShape = { projectId: z.string().describe("Atlas project ID"), ipAddresses: z diff --git a/src/tools/atlas/createDBUser.ts b/src/tools/atlas/createDBUser.ts index 2698f0d8..d1d7d635 100644 --- a/src/tools/atlas/createDBUser.ts +++ b/src/tools/atlas/createDBUser.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { CloudDatabaseUser, DatabaseUserRole } from "../../common/atlas/openapi.js"; export class CreateDBUserTool extends AtlasToolBase { protected name = "atlas-create-db-user"; protected description = "Create an MongoDB Atlas database user"; + protected operationType: OperationType = "create"; protected argsShape = { projectId: z.string().describe("Atlas project ID"), username: z.string().describe("Username for the new user"), diff --git a/src/tools/atlas/createFreeCluster.ts b/src/tools/atlas/createFreeCluster.ts index 8179883f..a32147fe 100644 --- a/src/tools/atlas/createFreeCluster.ts +++ b/src/tools/atlas/createFreeCluster.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { ClusterDescription20240805 } from "../../common/atlas/openapi.js"; export class CreateFreeClusterTool extends AtlasToolBase { protected name = "atlas-create-free-cluster"; protected description = "Create a free MongoDB Atlas cluster"; + protected operationType: OperationType = "create"; protected argsShape = { projectId: z.string().describe("Atlas project ID to create the cluster in"), name: z.string().describe("Name of the cluster"), diff --git a/src/tools/atlas/inspectAccessList.ts b/src/tools/atlas/inspectAccessList.ts index c66cf5dc..ea2a5903 100644 --- a/src/tools/atlas/inspectAccessList.ts +++ b/src/tools/atlas/inspectAccessList.ts @@ -1,11 +1,12 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; export class InspectAccessListTool extends AtlasToolBase { protected name = "atlas-inspect-access-list"; protected description = "Inspect Ip/CIDR ranges with access to your MongoDB Atlas clusters."; + protected operationType: OperationType = "read"; protected argsShape = { projectId: z.string().describe("Atlas project ID"), }; diff --git a/src/tools/atlas/inspectCluster.ts b/src/tools/atlas/inspectCluster.ts index 9ad35a46..f46fcb44 100644 --- a/src/tools/atlas/inspectCluster.ts +++ b/src/tools/atlas/inspectCluster.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { ClusterDescription20240805 } from "../../common/atlas/openapi.js"; export class InspectClusterTool extends AtlasToolBase { protected name = "atlas-inspect-cluster"; protected description = "Inspect MongoDB Atlas cluster"; + protected operationType: OperationType = "read"; protected argsShape = { projectId: z.string().describe("Atlas project ID"), clusterName: z.string().describe("Atlas cluster name"), diff --git a/src/tools/atlas/listClusters.ts b/src/tools/atlas/listClusters.ts index eb38f74e..d26c9e26 100644 --- a/src/tools/atlas/listClusters.ts +++ b/src/tools/atlas/listClusters.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { PaginatedClusterDescription20240805, PaginatedOrgGroupView, Group } from "../../common/atlas/openapi.js"; export class ListClustersTool extends AtlasToolBase { protected name = "atlas-list-clusters"; protected description = "List MongoDB Atlas clusters"; + protected operationType: OperationType = "read"; protected argsShape = { projectId: z.string().describe("Atlas project ID to filter clusters").optional(), }; diff --git a/src/tools/atlas/listDBUsers.ts b/src/tools/atlas/listDBUsers.ts index d9712b1e..5d9a476d 100644 --- a/src/tools/atlas/listDBUsers.ts +++ b/src/tools/atlas/listDBUsers.ts @@ -1,12 +1,13 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; -import { ToolArgs } from "../tool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { DatabaseUserRole, UserScope } from "../../common/atlas/openapi.js"; export class ListDBUsersTool extends AtlasToolBase { protected name = "atlas-list-db-users"; protected description = "List MongoDB Atlas database users"; + protected operationType: OperationType = "read"; protected argsShape = { projectId: z.string().describe("Atlas project ID to filter DB users"), }; diff --git a/src/tools/atlas/listProjects.ts b/src/tools/atlas/listProjects.ts index 17fa6668..843f216c 100644 --- a/src/tools/atlas/listProjects.ts +++ b/src/tools/atlas/listProjects.ts @@ -1,9 +1,11 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AtlasToolBase } from "./atlasTool.js"; +import { OperationType } from "../tool.js"; export class ListProjectsTool extends AtlasToolBase { protected name = "atlas-list-projects"; protected description = "List MongoDB Atlas projects"; + protected operationType: OperationType = "read"; protected argsShape = {}; protected async execute(): Promise { diff --git a/src/tools/mongodb/collectionIndexes.ts b/src/tools/mongodb/collectionIndexes.ts index 4d8cae90..08126aff 100644 --- a/src/tools/mongodb/collectionIndexes.ts +++ b/src/tools/mongodb/collectionIndexes.ts @@ -1,12 +1,12 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js"; -import { ToolArgs } from "../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "./mongodbTool.js"; +import { ToolArgs, OperationType } from "../tool.js"; export class CollectionIndexesTool extends MongoDBToolBase { protected name = "collection-indexes"; protected description = "Describe the indexes for a collection"; protected argsShape = DbOperationArgs; - protected operationType: DbOperationType = "read"; + protected operationType: OperationType = "read"; protected async execute({ database, collection }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/connect.ts b/src/tools/mongodb/connect.ts index dfba9926..93946311 100644 --- a/src/tools/mongodb/connect.ts +++ b/src/tools/mongodb/connect.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "./mongodbTool.js"; -import { ToolArgs } from "../tool.js"; +import { MongoDBToolBase } from "./mongodbTool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { ErrorCodes, MongoDBError } from "../../errors.js"; import config from "../../config.js"; @@ -15,7 +15,7 @@ export class ConnectTool extends MongoDBToolBase { .describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format) or cluster name"), }; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute({ connectionStringOrClusterName, diff --git a/src/tools/mongodb/create/insertMany.ts b/src/tools/mongodb/create/insertMany.ts index fdf1dcbc..a09de18d 100644 --- a/src/tools/mongodb/create/insertMany.ts +++ b/src/tools/mongodb/create/insertMany.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class InsertManyTool extends MongoDBToolBase { protected name = "insert-many"; @@ -14,7 +14,7 @@ export class InsertManyTool extends MongoDBToolBase { "The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()" ), }; - protected operationType: DbOperationType = "create"; + protected operationType: OperationType = "create"; protected async execute({ database, diff --git a/src/tools/mongodb/create/insertOne.ts b/src/tools/mongodb/create/insertOne.ts index b490efd4..bc780ae3 100644 --- a/src/tools/mongodb/create/insertOne.ts +++ b/src/tools/mongodb/create/insertOne.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class InsertOneTool extends MongoDBToolBase { protected name = "insert-one"; @@ -16,7 +16,7 @@ export class InsertOneTool extends MongoDBToolBase { ), }; - protected operationType: DbOperationType = "create"; + protected operationType: OperationType = "create"; protected async execute({ database, diff --git a/src/tools/mongodb/createIndex.ts b/src/tools/mongodb/createIndex.ts index 30bc17af..8b49b6bf 100644 --- a/src/tools/mongodb/createIndex.ts +++ b/src/tools/mongodb/createIndex.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js"; -import { ToolArgs } from "../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "./mongodbTool.js"; +import { ToolArgs, OperationType } from "../tool.js"; import { IndexDirection } from "mongodb"; export class CreateIndexTool extends MongoDBToolBase { @@ -12,7 +12,7 @@ export class CreateIndexTool extends MongoDBToolBase { keys: z.record(z.string(), z.custom()).describe("The index definition"), }; - protected operationType: DbOperationType = "create"; + protected operationType: OperationType = "create"; protected async execute({ database, collection, keys }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/delete/deleteMany.ts b/src/tools/mongodb/delete/deleteMany.ts index 5702b54a..834b2aaa 100644 --- a/src/tools/mongodb/delete/deleteMany.ts +++ b/src/tools/mongodb/delete/deleteMany.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class DeleteManyTool extends MongoDBToolBase { protected name = "delete-many"; @@ -16,7 +16,7 @@ export class DeleteManyTool extends MongoDBToolBase { "The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()" ), }; - protected operationType: DbOperationType = "delete"; + protected operationType: OperationType = "delete"; protected async execute({ database, diff --git a/src/tools/mongodb/delete/deleteOne.ts b/src/tools/mongodb/delete/deleteOne.ts index 3c9a2e57..137d5351 100644 --- a/src/tools/mongodb/delete/deleteOne.ts +++ b/src/tools/mongodb/delete/deleteOne.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class DeleteOneTool extends MongoDBToolBase { protected name = "delete-one"; @@ -16,7 +16,7 @@ export class DeleteOneTool extends MongoDBToolBase { "The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()" ), }; - protected operationType: DbOperationType = "delete"; + protected operationType: OperationType = "delete"; protected async execute({ database, diff --git a/src/tools/mongodb/delete/dropCollection.ts b/src/tools/mongodb/delete/dropCollection.ts index b6444da6..9ed1a7c8 100644 --- a/src/tools/mongodb/delete/dropCollection.ts +++ b/src/tools/mongodb/delete/dropCollection.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class DropCollectionTool extends MongoDBToolBase { protected name = "drop-collection"; @@ -9,7 +9,7 @@ export class DropCollectionTool extends MongoDBToolBase { protected argsShape = { ...DbOperationArgs, }; - protected operationType: DbOperationType = "delete"; + protected operationType: OperationType = "delete"; protected async execute({ database, collection }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/delete/dropDatabase.ts b/src/tools/mongodb/delete/dropDatabase.ts index 4b126b2c..6a58345d 100644 --- a/src/tools/mongodb/delete/dropDatabase.ts +++ b/src/tools/mongodb/delete/dropDatabase.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class DropDatabaseTool extends MongoDBToolBase { protected name = "drop-database"; @@ -8,7 +8,7 @@ export class DropDatabaseTool extends MongoDBToolBase { protected argsShape = { database: DbOperationArgs.database, }; - protected operationType: DbOperationType = "delete"; + protected operationType: OperationType = "delete"; protected async execute({ database }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/metadata/collectionSchema.ts b/src/tools/mongodb/metadata/collectionSchema.ts index f780252d..dd65e2a6 100644 --- a/src/tools/mongodb/metadata/collectionSchema.ts +++ b/src/tools/mongodb/metadata/collectionSchema.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; import { parseSchema, SchemaField } from "mongodb-schema"; export class CollectionSchemaTool extends MongoDBToolBase { @@ -8,7 +8,7 @@ export class CollectionSchemaTool extends MongoDBToolBase { protected description = "Describe the schema for a collection"; protected argsShape = DbOperationArgs; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute({ database, collection }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/metadata/collectionStorageSize.ts b/src/tools/mongodb/metadata/collectionStorageSize.ts index ab41261d..6d96f2a3 100644 --- a/src/tools/mongodb/metadata/collectionStorageSize.ts +++ b/src/tools/mongodb/metadata/collectionStorageSize.ts @@ -1,13 +1,13 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class CollectionStorageSizeTool extends MongoDBToolBase { protected name = "collection-storage-size"; protected description = "Gets the size of the collection in MB"; protected argsShape = DbOperationArgs; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute({ database, collection }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/metadata/dbStats.ts b/src/tools/mongodb/metadata/dbStats.ts index 1223b25b..979b17e2 100644 --- a/src/tools/mongodb/metadata/dbStats.ts +++ b/src/tools/mongodb/metadata/dbStats.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class DbStatsTool extends MongoDBToolBase { protected name = "db-stats"; @@ -9,7 +9,7 @@ export class DbStatsTool extends MongoDBToolBase { database: DbOperationArgs.database, }; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute({ database }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/metadata/listCollections.ts b/src/tools/mongodb/metadata/listCollections.ts index 2fc95d3d..09a4f04c 100644 --- a/src/tools/mongodb/metadata/listCollections.ts +++ b/src/tools/mongodb/metadata/listCollections.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class ListCollectionsTool extends MongoDBToolBase { protected name = "list-collections"; @@ -9,7 +9,7 @@ export class ListCollectionsTool extends MongoDBToolBase { database: DbOperationArgs.database, }; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute({ database }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/metadata/listDatabases.ts b/src/tools/mongodb/metadata/listDatabases.ts index 7a89d09c..0c1cc476 100644 --- a/src/tools/mongodb/metadata/listDatabases.ts +++ b/src/tools/mongodb/metadata/listDatabases.ts @@ -1,13 +1,14 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; import * as bson from "bson"; +import { OperationType } from "../../tool.js"; export class ListDatabasesTool extends MongoDBToolBase { protected name = "list-databases"; protected description = "List all databases for a MongoDB connection"; protected argsShape = {}; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "metadata"; protected async execute(): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/mongodbTool.ts b/src/tools/mongodb/mongodbTool.ts index 9c09caf0..a2b120da 100644 --- a/src/tools/mongodb/mongodbTool.ts +++ b/src/tools/mongodb/mongodbTool.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { ToolBase } from "../tool.js"; +import { ToolBase, ToolCategory } from "../tool.js"; import { State } from "../../state.js"; import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; @@ -11,14 +11,12 @@ export const DbOperationArgs = { collection: z.string().describe("Collection name"), }; -export type DbOperationType = "metadata" | "read" | "create" | "update" | "delete"; - export abstract class MongoDBToolBase extends ToolBase { constructor(state: State) { super(state); } - protected abstract operationType: DbOperationType; + protected category: ToolCategory = "mongodb"; protected async ensureConnected(): Promise { const provider = this.state.serviceProvider; diff --git a/src/tools/mongodb/read/aggregate.ts b/src/tools/mongodb/read/aggregate.ts index 66bc1edb..41fe52d3 100644 --- a/src/tools/mongodb/read/aggregate.ts +++ b/src/tools/mongodb/read/aggregate.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class AggregateTool extends MongoDBToolBase { protected name = "aggregate"; @@ -12,7 +12,7 @@ export class AggregateTool extends MongoDBToolBase { pipeline: z.array(z.object({}).passthrough()).describe("An array of aggregation stages to execute"), limit: z.number().optional().default(10).describe("The maximum number of documents to return"), }; - protected operationType: DbOperationType = "read"; + protected operationType: OperationType = "read"; protected async execute({ database, diff --git a/src/tools/mongodb/read/count.ts b/src/tools/mongodb/read/count.ts index 8c5f446d..60d4fb8d 100644 --- a/src/tools/mongodb/read/count.ts +++ b/src/tools/mongodb/read/count.ts @@ -1,6 +1,6 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; import { z } from "zod"; export class CountTool extends MongoDBToolBase { @@ -17,7 +17,7 @@ export class CountTool extends MongoDBToolBase { ), }; - protected operationType: DbOperationType = "metadata"; + protected operationType: OperationType = "read"; protected async execute({ database, collection, query }: ToolArgs): Promise { const provider = await this.ensureConnected(); diff --git a/src/tools/mongodb/read/find.ts b/src/tools/mongodb/read/find.ts index 54edce8e..cb33936e 100644 --- a/src/tools/mongodb/read/find.ts +++ b/src/tools/mongodb/read/find.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; import { SortDirection } from "mongodb"; export class FindTool extends MongoDBToolBase { @@ -28,7 +28,7 @@ export class FindTool extends MongoDBToolBase { "A document, describing the sort order, matching the syntax of the sort argument of cursor.sort()" ), }; - protected operationType: DbOperationType = "read"; + protected operationType: OperationType = "read"; protected async execute({ database, diff --git a/src/tools/mongodb/update/renameCollection.ts b/src/tools/mongodb/update/renameCollection.ts index e0c83875..d513fef4 100644 --- a/src/tools/mongodb/update/renameCollection.ts +++ b/src/tools/mongodb/update/renameCollection.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class RenameCollectionTool extends MongoDBToolBase { protected name = "rename-collection"; @@ -12,7 +12,7 @@ export class RenameCollectionTool extends MongoDBToolBase { newName: z.string().describe("The new name for the collection"), dropTarget: z.boolean().optional().default(false).describe("If true, drops the target collection if it exists"), }; - protected operationType: DbOperationType = "update"; + protected operationType: OperationType = "update"; protected async execute({ database, diff --git a/src/tools/mongodb/update/updateMany.ts b/src/tools/mongodb/update/updateMany.ts index e692c36b..bfdf9f2f 100644 --- a/src/tools/mongodb/update/updateMany.ts +++ b/src/tools/mongodb/update/updateMany.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class UpdateManyTool extends MongoDBToolBase { protected name = "update-many"; @@ -26,7 +26,7 @@ export class UpdateManyTool extends MongoDBToolBase { .optional() .describe("Controls whether to insert a new document if no documents match the filter"), }; - protected operationType: DbOperationType = "update"; + protected operationType: OperationType = "update"; protected async execute({ database, diff --git a/src/tools/mongodb/update/updateOne.ts b/src/tools/mongodb/update/updateOne.ts index b1a604c6..f7d555b1 100644 --- a/src/tools/mongodb/update/updateOne.ts +++ b/src/tools/mongodb/update/updateOne.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js"; -import { ToolArgs } from "../../tool.js"; +import { MongoDBToolBase } from "../mongodbTool.js"; +import { ToolArgs, OperationType } from "../../tool.js"; export class UpdateOneTool extends MongoDBToolBase { protected name = "update-one"; @@ -26,7 +26,7 @@ export class UpdateOneTool extends MongoDBToolBase { .optional() .describe("Controls whether to insert a new document if no documents match the filter"), }; - protected operationType: DbOperationType = "update"; + protected operationType: OperationType = "update"; protected async execute({ database, diff --git a/src/tools/tool.ts b/src/tools/tool.ts index c1889be5..0f35d594 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -4,12 +4,20 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { State } from "../state.js"; import logger from "../logger.js"; import { mongoLogId } from "mongodb-log-writer"; +import config from "../config.js"; export type ToolArgs = z.objectOutputType; +export type OperationType = "metadata" | "read" | "create" | "delete" | "update" | "cluster"; +export type ToolCategory = "mongodb" | "atlas"; + export abstract class ToolBase { protected abstract name: string; + protected abstract category: ToolCategory; + + protected abstract operationType: OperationType; + protected abstract description: string; protected abstract argsShape: ZodRawShape; @@ -21,6 +29,11 @@ export abstract class ToolBase { public register(server: McpServer): void { const callback = async (args: ToolArgs): Promise => { try { + const preventionResult = this.verifyAllowed(); + if (preventionResult) { + return preventionResult; + } + // TODO: add telemetry here logger.debug( mongoLogId(1_000_006), @@ -46,6 +59,35 @@ export abstract class ToolBase { } } + // Checks if a tool is allowed to run based on the config + private verifyAllowed(): CallToolResult | undefined { + let errorClarification: string | undefined; + if (config.disabledTools.includes(this.category)) { + errorClarification = `its category, \`${this.category}\`,`; + } else if (config.disabledTools.includes(this.operationType)) { + errorClarification = `its operation type, \`${this.operationType}\`,`; + } else if (config.disabledTools.includes(this.name)) { + errorClarification = `it`; + } + + if (errorClarification) { + logger.debug( + mongoLogId(1_000_010), + "tool", + `Prevented execution of ${this.name} because ${errorClarification} is disabled in the config` + ); + return { + content: [ + { + type: "text", + text: `Cannot execute tool \`${this.name}\` because ${errorClarification} is disabled in the config.`, + }, + ], + isError: true, + }; + } + } + // This method is intended to be overridden by subclasses to handle errors protected handleError(error: unknown): Promise | CallToolResult { return { From 13c2604c7dd57123d8b77301123109c8ee0c5500 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 11 Apr 2025 17:11:32 +0200 Subject: [PATCH 2/5] Prevent tool registration rather than reject execution at runtime --- src/tools/tool.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 6d8699b3..0fe6e80f 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -27,13 +27,12 @@ export abstract class ToolBase { protected constructor(protected session: Session) {} public register(server: McpServer): void { + if (!this.verifyAllowed()) { + return; + } + const callback: ToolCallback = async (...args) => { try { - const preventionResult = this.verifyAllowed(); - if (preventionResult) { - return preventionResult; - } - // TODO: add telemetry here logger.debug( mongoLogId(1_000_006), @@ -53,7 +52,7 @@ export abstract class ToolBase { } // Checks if a tool is allowed to run based on the config - private verifyAllowed(): CallToolResult | undefined { + private verifyAllowed(): boolean { let errorClarification: string | undefined; if (config.disabledTools.includes(this.category)) { errorClarification = `its category, \`${this.category}\`,`; @@ -67,18 +66,13 @@ export abstract class ToolBase { logger.debug( mongoLogId(1_000_010), "tool", - `Prevented execution of ${this.name} because ${errorClarification} is disabled in the config` + `Prevented registration of ${this.name} because ${errorClarification} is disabled in the config` ); - return { - content: [ - { - type: "text", - text: `Cannot execute tool \`${this.name}\` because ${errorClarification} is disabled in the config.`, - }, - ], - isError: true, - }; + + return false; } + + return true; } // This method is intended to be overridden by subclasses to handle errors From 0bdfc031111f63a3fe312bae2376b649148e5287 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 15 Apr 2025 09:50:28 +0200 Subject: [PATCH 3/5] Update readme --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b887870a..092728b2 100644 --- a/README.md +++ b/README.md @@ -166,12 +166,33 @@ The MongoDB MCP Server can be configured using multiple methods, with the follow | `apiClientSecret` | Atlas API client secret for authentication | | `connectionString` | MongoDB connection string for direct database connections (optional users may choose to inform it on every tool call) | | `logPath` | Folder to store logs | +| `disabledTools` | An array of tool names, operation types, and/or categories of tools that will be disabled. | -**Default Log Path:** - +#### `logPath` +Default log location is as follows: - Windows: `%LOCALAPPDATA%\mongodb\mongodb-mcp\.app-logs` - macOS/Linux: `~/.mongodb/mongodb-mcp/.app-logs` + +#### Disabled Tools +You can disable specific tools or categories of tools by using the `disabledTools` option. This option accepts an array of strings, +where each string can be a tool name, operation type, or category. + +The way the array is constructed depends on the type of configuration method you use: +- For **environment variable** configuration, use a comma-separated string: `export MDB_MCP_DISABLED_TOOLS="create,update,delete,atlas,collectionSchema"`. +- For **command-line argument** configuration, use a space-separated string: `--disabledTools create update delete atlas collectionSchema`. + +Categories of tools: +- `atlas` - MongoDB Atlas tools, such as list clusters, create cluster, etc. +- `mongodb` - MongoDB database tools, such as find, aggregate, etc. + +Operation types: +- `create` - Tools that create resources, such as create cluster, insert document, etc. +- `update` - Tools that update resources, such as update document, rename collection, etc. +- `delete` - Tools that delete resources, such as delete document, drop collection, etc. +- `read` - Tools that read resources, such as find, aggregate, list clusters, etc. +- `metadata` - Tools that read metadata, such as list databases, list collections, collection schema, etc. + ### Atlas API Access To use the Atlas API tools, you'll need to create a service account in MongoDB Atlas: From 0a8f834e5996c86c25506164f31b3827c6c398c8 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 15 Apr 2025 09:54:37 +0200 Subject: [PATCH 4/5] fix the readme format --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index eb767fc2..4043f6e6 100644 --- a/README.md +++ b/README.md @@ -148,24 +148,29 @@ The MongoDB MCP Server can be configured using multiple methods, with the follow | `disabledTools` | An array of tool names, operation types, and/or categories of tools that will be disabled. | #### `logPath` + Default log location is as follows: + - Windows: `%LOCALAPPDATA%\mongodb\mongodb-mcp\.app-logs` - macOS/Linux: `~/.mongodb/mongodb-mcp/.app-logs` - #### Disabled Tools + You can disable specific tools or categories of tools by using the `disabledTools` option. This option accepts an array of strings, where each string can be a tool name, operation type, or category. The way the array is constructed depends on the type of configuration method you use: + - For **environment variable** configuration, use a comma-separated string: `export MDB_MCP_DISABLED_TOOLS="create,update,delete,atlas,collectionSchema"`. - For **command-line argument** configuration, use a space-separated string: `--disabledTools create update delete atlas collectionSchema`. Categories of tools: + - `atlas` - MongoDB Atlas tools, such as list clusters, create cluster, etc. - `mongodb` - MongoDB database tools, such as find, aggregate, etc. Operation types: + - `create` - Tools that create resources, such as create cluster, insert document, etc. - `update` - Tools that update resources, such as update document, rename collection, etc. - `delete` - Tools that delete resources, such as delete document, drop collection, etc. From efa36ff6b205a00ec372d9853ea23dfe215fac98 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 16 Apr 2025 09:34:24 +0200 Subject: [PATCH 5/5] retry cluster start in a different folder --- tests/integration/helpers.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index 11963fc2..8fd3c009 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -62,8 +62,6 @@ export function jestTestMCPClient(): () => Client { export function jestTestCluster(): () => runner.MongoCluster { let cluster: runner.MongoCluster | undefined; - function runMongodb() {} - beforeAll(async function () { // Downloading Windows executables in CI takes a long time because // they include debug symbols... @@ -72,7 +70,7 @@ export function jestTestCluster(): () => runner.MongoCluster { // On Windows, we may have a situation where mongod.exe is not fully released by the OS // before we attempt to run it again, so we add a retry. - const dbsDir = path.join(tmpDir, "mongodb-runner", `dbs`); + let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs"); for (let i = 0; i < 10; i++) { try { cluster = await MongoCluster.start({ @@ -83,10 +81,21 @@ export function jestTestCluster(): () => runner.MongoCluster { return; } catch (err) { - console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`); - await new Promise((resolve) => setTimeout(resolve, 1000)); + if (i < 5) { + // Just wait a little bit and retry + console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } else { + // If we still fail after 5 seconds, try another db dir + console.error( + `Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}. Retrying with a new db dir.` + ); + dbsDir = path.join(tmpDir, "mongodb-runner", `dbs${i - 5}`); + } } } + + throw new Error("Failed to start cluster after 10 attempts"); }, 120_000); afterAll(async function () {