├── docker-compose.yml ├── tsconfig.json ├── src ├── mongodb │ ├── client.ts │ └── schema.ts ├── tools │ ├── collection │ │ └── list-collections.ts │ ├── indexes │ │ ├── list-indexes.ts │ │ ├── create-index.ts │ │ └── drop-index.ts │ ├── documents │ │ ├── delete-one.ts │ │ ├── insert-one.ts │ │ ├── find.ts │ │ └── update-one.ts │ ├── base │ │ └── tool.ts │ └── registry.ts ├── index.ts └── seed.ts ├── smithery.yaml ├── Dockerfile ├── LICENSE ├── package.json ├── README.md └── .gitignore /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | mongodb: 4 | image: mongo:latest 5 | ports: 6 | - "27017:27017" 7 | environment: 8 | MONGO_INITDB_ROOT_USERNAME: root 9 | MONGO_INITDB_ROOT_PASSWORD: example 10 | volumes: 11 | - mongodb_data:/data/db 12 | 13 | volumes: 14 | mongodb_data: 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "declaration": true 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", "dist"] 14 | } 15 | -------------------------------------------------------------------------------- /src/mongodb/client.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Db } from "mongodb"; 2 | 3 | export let client: MongoClient; 4 | export let db: Db; 5 | 6 | export async function connectToMongoDB(databaseUrl: string) { 7 | try { 8 | client = new MongoClient(databaseUrl); 9 | await client.connect(); 10 | const resourceBaseUrl = new URL(databaseUrl); 11 | const dbName = resourceBaseUrl.pathname.split("/")[1] || "test"; 12 | console.error(`Connecting to database: ${dbName}`); 13 | db = client.db(dbName); 14 | } catch (error) { 15 | console.error("MongoDB connection error:", error); 16 | throw error; 17 | } 18 | } 19 | 20 | export async function closeMongoDB() { 21 | await client?.close(); 22 | } 23 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - mongoConnectionUrl 10 | properties: 11 | mongoConnectionUrl: 12 | type: string 13 | description: MongoDB connection URL in the format 14 | mongodb://:@:/?authSource=admin 15 | commandFunction: 16 | # A function that produces the CLI command to start the MCP on stdio. 17 | |- 18 | config => ({ command: 'node', args: ['dist/index.js', config.mongoConnectionUrl] }) 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use Node.js 18+ as the base image 3 | FROM node:18-alpine AS builder 4 | 5 | # Set the working directory inside the container 6 | WORKDIR /app 7 | 8 | # Copy package.json and package-lock.json to install dependencies 9 | COPY package.json package-lock.json ./ 10 | 11 | # Install dependencies 12 | RUN npm install --ignore-scripts 13 | 14 | # Copy the rest of the application code 15 | COPY . . 16 | 17 | # Build the project 18 | RUN npm run build 19 | 20 | # Use a minimal Node.js image for the runtime 21 | FROM node:18-alpine 22 | 23 | # Set the working directory inside the container 24 | WORKDIR /app 25 | 26 | # Copy the built files from the builder stage 27 | COPY --from=builder /app/dist /app/dist 28 | COPY --from=builder /app/package.json /app/package-lock.json /app/node_modules ./ 29 | 30 | # Specify the command to run the server 31 | ENTRYPOINT ["node", "dist/index.js"] 32 | -------------------------------------------------------------------------------- /src/tools/collection/list-collections.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | type ListCollectionsParams = ToolParams; 5 | 6 | export class ListCollectionsTool extends BaseTool { 7 | name = "listCollections"; 8 | description = "List all available collections in the database"; 9 | inputSchema = { 10 | type: "object" as const, 11 | properties: {}, 12 | }; 13 | 14 | async execute(_params: ListCollectionsParams) { 15 | try { 16 | const collections = await db.listCollections().toArray(); 17 | return { 18 | content: [ 19 | { 20 | type: "text" as const, 21 | text: JSON.stringify( 22 | collections.map((c) => ({ 23 | name: c.name, 24 | type: c.type, 25 | })), 26 | null, 27 | 2 28 | ), 29 | }, 30 | ], 31 | isError: false, 32 | }; 33 | } catch (error) { 34 | return this.handleError(error); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alex Andrushevich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/tools/indexes/list-indexes.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | interface ListIndexesParams extends ToolParams { 5 | collection: string; 6 | [key: string]: unknown; 7 | } 8 | 9 | export class ListIndexesTool extends BaseTool { 10 | name = "indexes"; 11 | description = "List indexes for a collection"; 12 | inputSchema = { 13 | type: "object" as const, 14 | properties: { 15 | collection: { 16 | type: "string", 17 | description: "Name of the collection", 18 | }, 19 | }, 20 | required: ["collection"], 21 | }; 22 | 23 | async execute(params: ListIndexesParams) { 24 | try { 25 | const collection = this.validateCollection(params.collection); 26 | const indexes = await db.collection(collection).indexes(); 27 | return { 28 | content: [ 29 | { 30 | type: "text" as const, 31 | text: JSON.stringify(indexes, null, 2), 32 | }, 33 | ], 34 | isError: false, 35 | }; 36 | } catch (error) { 37 | return this.handleError(error); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongo-mcp", 3 | "version": "0.2.0", 4 | "author": { 5 | "name": "Alex Andru", 6 | "email": "alex007d@gmail.com" 7 | }, 8 | "description": "MCP server for interacting with MongoDB databases", 9 | "license": "MIT", 10 | "type": "module", 11 | "bin": { 12 | "mongodb-mcp": "dist/index.js" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "build": "tsc && shx chmod +x dist/*.js", 19 | "prepare": "npm run build", 20 | "watch": "tsc --watch", 21 | "seed": "tsx src/seed.ts" 22 | }, 23 | "keywords": [ 24 | "mcp", 25 | "claude", 26 | "mongodb", 27 | "anthropic", 28 | "ai", 29 | "database" 30 | ], 31 | "dependencies": { 32 | "@modelcontextprotocol/sdk": "0.6.0", 33 | "mongodb": "^6.3.0" 34 | }, 35 | "devDependencies": { 36 | "@types/node": "^20.10.5", 37 | "shx": "^0.3.4", 38 | "tsx": "^4.19.2", 39 | "typescript": "^5.3.3" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/QuantGeekDev/mongo-mcp.git" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/QuantGeekDev/mongo-mcp/issues" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tools/documents/delete-one.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | interface DeleteOneParams extends ToolParams { 5 | collection: string; 6 | filter: Record; 7 | [key: string]: unknown; 8 | } 9 | 10 | export class DeleteOneTool extends BaseTool { 11 | name = "deleteOne"; 12 | description = "Delete a single document from a collection"; 13 | inputSchema = { 14 | type: "object" as const, 15 | properties: { 16 | collection: { 17 | type: "string", 18 | description: "Name of the collection", 19 | }, 20 | filter: { 21 | type: "object", 22 | description: "Filter to identify document", 23 | }, 24 | }, 25 | required: ["collection", "filter"], 26 | }; 27 | 28 | async execute(params: DeleteOneParams) { 29 | try { 30 | const collection = this.validateCollection(params.collection); 31 | const filter = this.validateObject(params.filter, "Filter"); 32 | const result = await db.collection(collection).deleteOne(filter); 33 | 34 | return { 35 | content: [ 36 | { 37 | type: "text" as const, 38 | text: JSON.stringify({ deleted: result.deletedCount }, null, 2), 39 | }, 40 | ], 41 | isError: false, 42 | }; 43 | } catch (error) { 44 | return this.handleError(error); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tools/indexes/create-index.ts: -------------------------------------------------------------------------------- 1 | import { BaseTool, ToolParams } from "../base/tool.js"; 2 | import { IndexDirection } from "mongodb"; 3 | import { db } from "../../mongodb/client.js"; 4 | 5 | interface CreateIndexParams extends ToolParams { 6 | collection: string; 7 | indexSpec: { [key: string]: IndexDirection }; 8 | [key: string]: unknown; 9 | } 10 | 11 | export class CreateIndexTool extends BaseTool { 12 | name = "createIndex"; 13 | description = "Create a new index on a collection"; 14 | inputSchema = { 15 | type: "object" as const, 16 | properties: { 17 | collection: { 18 | type: "string", 19 | description: "Name of the collection", 20 | }, 21 | indexSpec: { 22 | type: "object", 23 | description: 24 | "Index specification (e.g., { field: 1 } for ascending index)", 25 | }, 26 | }, 27 | required: ["collection", "indexSpec"], 28 | }; 29 | 30 | async execute(params: CreateIndexParams) { 31 | try { 32 | const collection = this.validateCollection(params.collection); 33 | const indexName = await db 34 | .collection(collection) 35 | .createIndex(params.indexSpec); 36 | 37 | return { 38 | content: [ 39 | { 40 | type: "text" as const, 41 | text: JSON.stringify({ indexName }, null, 2), 42 | }, 43 | ], 44 | isError: false, 45 | }; 46 | } catch (error) { 47 | return this.handleError(error); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tools/indexes/drop-index.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | interface DropIndexParams extends ToolParams { 5 | collection: string; 6 | indexName: string; 7 | [key: string]: unknown; 8 | } 9 | 10 | export class DropIndexTool extends BaseTool { 11 | name = "dropIndex"; 12 | description = "Drop an index from a collection"; 13 | inputSchema = { 14 | type: "object" as const, 15 | properties: { 16 | collection: { 17 | type: "string", 18 | description: "Name of the collection", 19 | }, 20 | indexName: { 21 | type: "string", 22 | description: "Name of the index to drop", 23 | }, 24 | }, 25 | required: ["collection", "indexName"], 26 | }; 27 | 28 | async execute(params: DropIndexParams) { 29 | try { 30 | const collection = this.validateCollection(params.collection); 31 | if (typeof params.indexName !== "string") { 32 | return this.handleError(new Error("Index name must be a string")); 33 | } 34 | 35 | const result = await db 36 | .collection(collection) 37 | .dropIndex(params.indexName); 38 | return { 39 | content: [ 40 | { 41 | type: "text" as const, 42 | text: JSON.stringify(result, null, 2), 43 | }, 44 | ], 45 | isError: false, 46 | }; 47 | } catch (error) { 48 | return this.handleError(error); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tools/base/tool.ts: -------------------------------------------------------------------------------- 1 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; 2 | 3 | export interface ToolResponse { 4 | content: { 5 | type: "text"; 6 | text: string; 7 | }[]; 8 | isError: boolean; 9 | _meta?: Record; 10 | } 11 | 12 | export type ToolParams = { 13 | [key: string]: unknown; 14 | }; 15 | 16 | export abstract class BaseTool { 17 | abstract name: string; 18 | abstract description: string; 19 | abstract inputSchema: { 20 | type: "object"; 21 | properties: Record; 22 | required?: string[]; 23 | }; 24 | 25 | abstract execute(params: T): Promise; 26 | 27 | protected validateCollection(collection: unknown): string { 28 | if (typeof collection !== "string") { 29 | throw new McpError( 30 | ErrorCode.InvalidRequest, 31 | `Collection name must be a string, got ${typeof collection}` 32 | ); 33 | } 34 | return collection; 35 | } 36 | 37 | protected validateObject( 38 | value: unknown, 39 | name: string 40 | ): Record { 41 | if (!value || typeof value !== "object") { 42 | throw new McpError(ErrorCode.InvalidRequest, `${name} must be an object`); 43 | } 44 | return value as Record; 45 | } 46 | 47 | protected handleError(error: unknown): ToolResponse { 48 | return { 49 | content: [ 50 | { 51 | type: "text", 52 | text: error instanceof Error ? error.message : String(error), 53 | }, 54 | ], 55 | isError: true, 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/tools/documents/insert-one.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | interface InsertOneParams extends ToolParams { 5 | collection: string; 6 | document: Record; 7 | [key: string]: unknown; 8 | } 9 | 10 | export class InsertOneTool extends BaseTool { 11 | name = "insertOne"; 12 | description = "Insert a single document into a collection"; 13 | inputSchema = { 14 | type: "object" as const, 15 | properties: { 16 | collection: { 17 | type: "string", 18 | description: "Name of the collection", 19 | }, 20 | document: { 21 | type: "object", 22 | description: "Document to insert", 23 | }, 24 | }, 25 | required: ["collection", "document"], 26 | }; 27 | 28 | async execute(params: InsertOneParams) { 29 | try { 30 | const collection = this.validateCollection(params.collection); 31 | const document = this.validateObject(params.document, "Document"); 32 | const result = await db.collection(collection).insertOne(document); 33 | 34 | return { 35 | content: [ 36 | { 37 | type: "text" as const, 38 | text: JSON.stringify( 39 | { 40 | acknowledged: result.acknowledged, 41 | insertedId: result.insertedId, 42 | }, 43 | null, 44 | 2 45 | ), 46 | }, 47 | ], 48 | isError: false, 49 | }; 50 | } catch (error) { 51 | return this.handleError(error); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/tools/documents/find.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | export interface FindParams extends ToolParams { 5 | collection: string; 6 | filter?: Record; 7 | limit?: number; 8 | projection?: Record; 9 | } 10 | 11 | export class FindTool extends BaseTool { 12 | name = "find"; 13 | description = "Query documents in a collection using MongoDB query syntax"; 14 | inputSchema = { 15 | type: "object" as const, 16 | properties: { 17 | collection: { 18 | type: "string", 19 | description: "Name of the collection to query", 20 | }, 21 | filter: { 22 | type: "object", 23 | description: "MongoDB query filter", 24 | default: {}, 25 | }, 26 | limit: { 27 | type: "number", 28 | description: "Maximum documents to return", 29 | default: 10, 30 | minimum: 1, 31 | maximum: 1000, 32 | }, 33 | projection: { 34 | type: "object", 35 | description: "Fields to include/exclude", 36 | default: {}, 37 | }, 38 | }, 39 | required: ["collection"], 40 | }; 41 | 42 | async execute(params: FindParams) { 43 | try { 44 | const collection = this.validateCollection(params.collection); 45 | const results = await db 46 | .collection(collection) 47 | .find(params.filter || {}) 48 | .project(params.projection || {}) 49 | .limit(Math.min(params.limit || 10, 1000)) 50 | .toArray(); 51 | 52 | return { 53 | content: [ 54 | { type: "text" as const, text: JSON.stringify(results, null, 2) }, 55 | ], 56 | isError: false, 57 | }; 58 | } catch (error) { 59 | return this.handleError(error); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tools/documents/update-one.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../../mongodb/client.js"; 2 | import { BaseTool, ToolParams } from "../base/tool.js"; 3 | 4 | interface UpdateOneParams extends ToolParams { 5 | collection: string; 6 | filter: Record; 7 | update: Record; 8 | [key: string]: unknown; 9 | } 10 | 11 | export class UpdateOneTool extends BaseTool { 12 | name = "updateOne"; 13 | description = "Update a single document in a collection"; 14 | inputSchema = { 15 | type: "object" as const, 16 | properties: { 17 | collection: { 18 | type: "string", 19 | description: "Name of the collection", 20 | }, 21 | filter: { 22 | type: "object", 23 | description: "Filter to identify document", 24 | }, 25 | update: { 26 | type: "object", 27 | description: "Update operations to apply", 28 | }, 29 | }, 30 | required: ["collection", "filter", "update"], 31 | }; 32 | 33 | async execute(params: UpdateOneParams) { 34 | try { 35 | const collection = this.validateCollection(params.collection); 36 | const filter = this.validateObject(params.filter, "Filter"); 37 | const update = this.validateObject(params.update, "Update"); 38 | const result = await db.collection(collection).updateOne(filter, update); 39 | 40 | return { 41 | content: [ 42 | { 43 | type: "text" as const, 44 | text: JSON.stringify( 45 | { 46 | matched: result.matchedCount, 47 | modified: result.modifiedCount, 48 | upsertedId: result.upsertedId, 49 | }, 50 | null, 51 | 2 52 | ), 53 | }, 54 | ], 55 | isError: false, 56 | }; 57 | } catch (error) { 58 | return this.handleError(error); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tools/registry.ts: -------------------------------------------------------------------------------- 1 | import { BaseTool } from "./base/tool.js"; 2 | import { ListCollectionsTool } from "./collection/list-collections.js"; 3 | import { DeleteOneTool } from "./documents/delete-one.js"; 4 | import { FindTool } from "./documents/find.js"; 5 | import { InsertOneTool } from "./documents/insert-one.js"; 6 | import { UpdateOneTool } from "./documents/update-one.js"; 7 | import { CreateIndexTool } from "./indexes/create-index.js"; 8 | import { DropIndexTool } from "./indexes/drop-index.js"; 9 | import { ListIndexesTool } from "./indexes/list-indexes.js"; 10 | import { McpError, ErrorCode, Tool } from "@modelcontextprotocol/sdk/types.js"; 11 | 12 | export class ToolRegistry { 13 | private tools: Map> = new Map(); 14 | 15 | constructor() { 16 | this.registerTool(new ListCollectionsTool()); 17 | this.registerTool(new FindTool()); 18 | this.registerTool(new InsertOneTool()); 19 | this.registerTool(new UpdateOneTool()); 20 | this.registerTool(new DeleteOneTool()); 21 | this.registerTool(new CreateIndexTool()); 22 | this.registerTool(new DropIndexTool()); 23 | this.registerTool(new ListIndexesTool()); 24 | } 25 | 26 | registerTool(tool: BaseTool) { 27 | this.tools.set(tool.name, tool); 28 | } 29 | 30 | getTool(name: string): BaseTool | undefined { 31 | const tool = this.tools.get(name); 32 | if (!tool) { 33 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); 34 | } 35 | return tool; 36 | } 37 | 38 | getAllTools(): BaseTool[] { 39 | return Array.from(this.tools.values()); 40 | } 41 | 42 | getToolSchemas(): Tool[] { 43 | return this.getAllTools().map((tool) => { 44 | const inputSchema = tool.inputSchema as any; 45 | return { 46 | name: tool.name, 47 | description: tool.description, 48 | inputSchema: { 49 | type: "object", 50 | properties: inputSchema.properties || {}, 51 | ...(inputSchema.required && { required: inputSchema.required }), 52 | }, 53 | }; 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { 5 | CallToolRequestSchema, 6 | ListToolsRequestSchema, 7 | } from "@modelcontextprotocol/sdk/types.js"; 8 | import { connectToMongoDB, closeMongoDB } from "./mongodb/client.js"; 9 | import { ToolRegistry } from "./tools/registry.js"; 10 | 11 | const args = process.argv.slice(2); 12 | if (args.length === 0) { 13 | console.error("Please provide a MongoDB connection URL"); 14 | process.exit(1); 15 | } 16 | const databaseUrl = args[0]; 17 | 18 | const toolRegistry = new ToolRegistry(); 19 | 20 | const server = new Server( 21 | { 22 | name: "mongodb-mcp", 23 | version: "0.1.0", 24 | }, 25 | { 26 | capabilities: { 27 | resources: {}, 28 | tools: { 29 | list: true, 30 | call: true, 31 | }, 32 | }, 33 | } 34 | ); 35 | 36 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ 37 | tools: toolRegistry.getToolSchemas(), 38 | _meta: {}, 39 | })); 40 | 41 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 42 | const name = request.params.name; 43 | const args = request.params.arguments ?? {}; 44 | 45 | try { 46 | console.error(`Executing tool: ${name}`); 47 | console.error(`Arguments: ${JSON.stringify(args, null, 2)}`); 48 | 49 | const tool = toolRegistry.getTool(name); 50 | if (!tool) { 51 | throw new Error(`Unknown tool: ${name}`); 52 | } 53 | 54 | const result = await tool.execute(args); 55 | return { toolResult: result }; 56 | } catch (error) { 57 | console.error("Operation failed:", error); 58 | return { 59 | toolResult: { 60 | content: [ 61 | { 62 | type: "text", 63 | text: error.message, 64 | }, 65 | ], 66 | isError: true, 67 | }, 68 | }; 69 | } 70 | }); 71 | 72 | async function runServer() { 73 | try { 74 | await connectToMongoDB(databaseUrl); 75 | const transport = new StdioServerTransport(); 76 | await server.connect(transport); 77 | console.error("MongoDB MCP server running on stdio"); 78 | } catch (error) { 79 | console.error("Failed to start server:", error); 80 | process.exit(1); 81 | } 82 | } 83 | 84 | process.on("SIGINT", async () => { 85 | try { 86 | await closeMongoDB(); 87 | } finally { 88 | process.exit(0); 89 | } 90 | }); 91 | 92 | process.on("unhandledRejection", (error) => { 93 | console.error("Unhandled promise rejection:", error); 94 | process.exit(1); 95 | }); 96 | 97 | runServer().catch(console.error); 98 | -------------------------------------------------------------------------------- /src/mongodb/schema.ts: -------------------------------------------------------------------------------- 1 | import { Collection } from "mongodb"; 2 | 3 | export interface MongoFieldSchema { 4 | field: string; 5 | type: string; 6 | isRequired: boolean; 7 | subFields?: MongoFieldSchema[]; 8 | } 9 | 10 | export interface MongoCollectionSchema { 11 | collection: string; 12 | fields: MongoFieldSchema[]; 13 | count: number; 14 | indexes?: unknown[]; 15 | } 16 | 17 | export function inferSchemaFromValue(value: unknown): string { 18 | if (value === null) return "null"; 19 | if (Array.isArray(value)) return "array"; 20 | if (value instanceof Date) return "date"; 21 | if (typeof value === "object") return "object"; 22 | return typeof value; 23 | } 24 | 25 | export function inferSchemaFromDocument( 26 | doc: Record, 27 | parentPath = "" 28 | ): MongoFieldSchema[] { 29 | const schema: MongoFieldSchema[] = []; 30 | 31 | for (const [key, value] of Object.entries(doc)) { 32 | const fieldPath = parentPath ? `${parentPath}.${key}` : key; 33 | const fieldType = inferSchemaFromValue(value); 34 | const field: MongoFieldSchema = { 35 | field: fieldPath, 36 | type: fieldType, 37 | isRequired: true, 38 | }; 39 | 40 | if (fieldType === "object" && value !== null) { 41 | field.subFields = inferSchemaFromDocument( 42 | value as Record, 43 | fieldPath 44 | ); 45 | } else if ( 46 | fieldType === "array" && 47 | Array.isArray(value) && 48 | value.length > 0 49 | ) { 50 | const arrayType = inferSchemaFromValue(value[0]); 51 | if (arrayType === "object") { 52 | field.subFields = inferSchemaFromDocument( 53 | value[0] as Record, 54 | `${fieldPath}[]` 55 | ); 56 | } 57 | } 58 | schema.push(field); 59 | } 60 | return schema; 61 | } 62 | 63 | export async function buildCollectionSchema( 64 | collection: Collection, 65 | sampleSize = 100 66 | ): Promise { 67 | const docs = (await collection 68 | .find({}) 69 | .limit(sampleSize) 70 | .toArray()) as Record[]; 71 | const count = await collection.countDocuments(); 72 | const indexes = await collection.indexes(); 73 | 74 | const fieldSchemas = new Map>(); 75 | const requiredFields = new Set(); 76 | 77 | docs.forEach((doc) => { 78 | const docSchema = inferSchemaFromDocument(doc); 79 | docSchema.forEach((field) => { 80 | if (!fieldSchemas.has(field.field)) { 81 | fieldSchemas.set(field.field, new Set()); 82 | } 83 | fieldSchemas.get(field.field)!.add(field.type); 84 | requiredFields.add(field.field); 85 | }); 86 | }); 87 | 88 | docs.forEach((doc) => { 89 | const docFields = new Set(Object.keys(doc)); 90 | for (const field of requiredFields) { 91 | if (!docFields.has(field.split(".")[0])) { 92 | requiredFields.delete(field); 93 | } 94 | } 95 | }); 96 | 97 | const fields: MongoFieldSchema[] = Array.from(fieldSchemas.entries()).map( 98 | ([field, types]) => ({ 99 | field, 100 | type: 101 | types.size === 1 102 | ? types.values().next().value 103 | : Array.from(types).join("|"), 104 | isRequired: requiredFields.has(field), 105 | subFields: undefined, 106 | }) 107 | ); 108 | 109 | for (const doc of docs) { 110 | const docSchema = inferSchemaFromDocument(doc); 111 | docSchema.forEach((fieldSchema) => { 112 | if (fieldSchema.subFields) { 113 | const existingField = fields.find((f) => f.field === fieldSchema.field); 114 | if (existingField && !existingField.subFields) { 115 | existingField.subFields = fieldSchema.subFields; 116 | } 117 | } 118 | }); 119 | } 120 | 121 | return { 122 | collection: collection.collectionName, 123 | fields, 124 | count, 125 | indexes, 126 | }; 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🗄️ MongoDB MCP Server for LLMS 2 | 3 | [![Node.js 18+](https://img.shields.io/badge/node-18%2B-blue.svg)](https://nodejs.org/en/) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | [![smithery badge](https://smithery.ai/badge/mongo-mcp)](https://smithery.ai/server/mongo-mcp) 6 | 7 | A Model Context Protocol (MCP) server that enables LLMs to interact directly with MongoDB databases. Query collections, inspect schemas, and manage data seamlessly through natural language. 8 | 9 | ## ✨ Features 10 | 11 | - 🔍 Collection schema inspection 12 | - 📊 Document querying and filtering 13 | - 📈 Index management 14 | - 📝 Document operations (insert, update, delete) 15 | 16 | ## Demo Video 17 | 18 | 19 | https://github.com/user-attachments/assets/2389bf23-a10d-49f9-bca9-2b39a1ebe654 20 | 21 | 22 | 23 | 24 | ## 🚀 Quick Start 25 | 26 | To get started, find your mongodb connection url and add this configuration to your Claude Desktop config file: 27 | 28 | **MacOS**: `~/Library/Application\ Support/Claude/claude_desktop_config.json` 29 | **Windows**: `%APPDATA%/Claude/claude_desktop_config.json` 30 | 31 | ```json 32 | { 33 | "mcpServers": { 34 | "mongodb": { 35 | "command": "npx", 36 | "args": [ 37 | "mongo-mcp", 38 | "mongodb://:@:/?authSource=admin" 39 | ] 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | ### Installing via Smithery 46 | 47 | To install MongoDB MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mongo-mcp): 48 | 49 | ```bash 50 | npx -y @smithery/cli install mongo-mcp --client claude 51 | ``` 52 | 53 | ### Prerequisites 54 | 55 | - Node.js 18+ 56 | - npx 57 | - Docker and Docker Compose (for local sandbox testing only) 58 | - MCP Client (Claude Desktop App for example) 59 | 60 | ### Test Sandbox Setup 61 | 62 | If you don't have a mongo db server to connect to and want to create a sample sandbox, follow these steps 63 | 64 | 1. Start MongoDB using Docker Compose: 65 | 66 | ```bash 67 | docker-compose up -d 68 | ``` 69 | 70 | 2. Seed the database with test data: 71 | 72 | ```bash 73 | npm run seed 74 | ``` 75 | 76 | ### Configure Claude Desktop 77 | 78 | Add this configuration to your Claude Desktop config file: 79 | 80 | **MacOS**: `~/Library/Application\ Support/Claude/claude_desktop_config.json` 81 | **Windows**: `%APPDATA%/Claude/claude_desktop_config.json` 82 | 83 | #### Local Development Mode: 84 | 85 | ```json 86 | { 87 | "mcpServers": { 88 | "mongodb": { 89 | "command": "node", 90 | "args": [ 91 | "dist/index.js", 92 | "mongodb://root:example@localhost:27017/test?authSource=admin" 93 | ] 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | ### Test Sandbox Data Structure 100 | 101 | The seed script creates three collections with sample data: 102 | 103 | #### Users 104 | 105 | - Personal info (name, email, age) 106 | - Nested address with coordinates 107 | - Arrays of interests 108 | - Membership dates 109 | 110 | #### Products 111 | 112 | - Product details (name, SKU, category) 113 | - Nested specifications 114 | - Price and inventory info 115 | - Tags and ratings 116 | 117 | #### Orders 118 | 119 | - Order details with items 120 | - User references 121 | - Shipping and payment info 122 | - Status tracking 123 | 124 | ## 🎯 Example Prompts 125 | 126 | Try these prompts with Claude to explore the functionality: 127 | 128 | ### Basic Operations 129 | 130 | ```plaintext 131 | "What collections are available in the database?" 132 | "Show me the schema for the users collection" 133 | "Find all users in San Francisco" 134 | ``` 135 | 136 | ### Advanced Queries 137 | 138 | ```plaintext 139 | "Find all electronics products that are in stock and cost less than $1000" 140 | "Show me all orders from the user john@example.com" 141 | "List the products with ratings above 4.5" 142 | ``` 143 | 144 | ### Index Management 145 | 146 | ```plaintext 147 | "What indexes exist on the users collection?" 148 | "Create an index on the products collection for the 'category' field" 149 | "List all indexes across all collections" 150 | ``` 151 | 152 | ### Document Operations 153 | 154 | ```plaintext 155 | "Insert a new product with name 'Gaming Laptop' in the products collection" 156 | "Update the status of order with ID X to 'shipped'" 157 | "Find and delete all products that are out of stock" 158 | ``` 159 | 160 | ## 📝 Available Tools 161 | 162 | The server provides these tools for database interaction: 163 | 164 | ### Query Tools 165 | 166 | - `find`: Query documents with filtering and projection 167 | - `listCollections`: List available collections 168 | - `insertOne`: Insert a single document 169 | - `updateOne`: Update a single document 170 | - `deleteOne`: Delete a single document 171 | 172 | ### Index Tools 173 | 174 | - `createIndex`: Create a new index 175 | - `dropIndex`: Remove an index 176 | - `indexes`: List indexes for a collection 177 | 178 | ## 📜 License 179 | 180 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 181 | -------------------------------------------------------------------------------- /src/seed.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, ObjectId } from "mongodb"; 2 | 3 | async function seed() { 4 | const client = new MongoClient( 5 | "mongodb://root:example@localhost:27017/admin" 6 | ); 7 | 8 | try { 9 | await client.connect(); 10 | const db = client.db("test"); 11 | 12 | await db 13 | .collection("users") 14 | .drop() 15 | .catch(() => {}); 16 | await db 17 | .collection("products") 18 | .drop() 19 | .catch(() => {}); 20 | await db 21 | .collection("orders") 22 | .drop() 23 | .catch(() => {}); 24 | 25 | await db.createCollection("users"); 26 | await db.collection("users").createIndex({ email: 1 }, { unique: true }); 27 | await db.collection("users").createIndex({ "address.city": 1 }); 28 | 29 | await db.createCollection("products"); 30 | await db.collection("products").createIndex({ sku: 1 }, { unique: true }); 31 | await db.collection("products").createIndex({ category: 1 }); 32 | 33 | await db.createCollection("orders"); 34 | await db.collection("orders").createIndex({ userId: 1 }); 35 | await db.collection("orders").createIndex({ orderDate: 1 }); 36 | 37 | const userIds = { 38 | john: new ObjectId(), 39 | jane: new ObjectId(), 40 | }; 41 | 42 | const users = [ 43 | { 44 | _id: userIds.john, 45 | email: "john@example.com", 46 | name: "John Doe", 47 | age: 30, 48 | address: { 49 | street: "123 Main St", 50 | city: "New York", 51 | country: "USA", 52 | coordinates: { 53 | lat: 40.7128, 54 | lng: -74.006, 55 | }, 56 | }, 57 | interests: ["sports", "technology"], 58 | memberSince: new Date("2023-01-01"), 59 | isActive: true, 60 | }, 61 | { 62 | _id: userIds.jane, 63 | email: "jane@example.com", 64 | name: "Jane Smith", 65 | age: 25, 66 | address: { 67 | street: "456 Market St", 68 | city: "San Francisco", 69 | country: "USA", 70 | coordinates: { 71 | lat: 37.7749, 72 | lng: -122.4194, 73 | }, 74 | }, 75 | interests: ["art", "music", "travel"], 76 | memberSince: new Date("2023-02-15"), 77 | isActive: true, 78 | }, 79 | ]; 80 | 81 | await db.collection("users").insertMany(users); 82 | 83 | const products = [ 84 | { 85 | _id: new ObjectId(), 86 | sku: "LAPTOP001", 87 | name: "Pro Laptop", 88 | category: "Electronics", 89 | price: 1299.99, 90 | specs: { 91 | cpu: "Intel i7", 92 | ram: "16GB", 93 | storage: "512GB SSD", 94 | }, 95 | inStock: true, 96 | tags: ["laptop", "computer", "work"], 97 | ratings: [4.5, 4.8, 4.2], 98 | lastUpdated: new Date(), 99 | }, 100 | { 101 | _id: new ObjectId(), 102 | sku: "PHONE001", 103 | name: "SmartPhone X", 104 | category: "Electronics", 105 | price: 699.99, 106 | specs: { 107 | screen: "6.1 inch", 108 | camera: "12MP", 109 | storage: "256GB", 110 | }, 111 | inStock: true, 112 | tags: ["phone", "mobile", "smart device"], 113 | ratings: [4.7, 4.6], 114 | lastUpdated: new Date(), 115 | }, 116 | { 117 | _id: new ObjectId(), 118 | sku: "BOOK001", 119 | name: "Database Design", 120 | category: "Books", 121 | price: 49.99, 122 | specs: { 123 | format: "Hardcover", 124 | pages: 500, 125 | language: "English", 126 | }, 127 | inStock: false, 128 | tags: ["education", "technology", "programming"], 129 | ratings: [4.9], 130 | lastUpdated: new Date(), 131 | }, 132 | ]; 133 | 134 | await db.collection("products").insertMany(products); 135 | 136 | const orders = [ 137 | { 138 | _id: new ObjectId(), 139 | userId: userIds.john, 140 | orderDate: new Date("2024-01-15"), 141 | status: "completed", 142 | items: [ 143 | { 144 | productSku: "LAPTOP001", 145 | quantity: 1, 146 | priceAtTime: 1299.99, 147 | }, 148 | { 149 | productSku: "BOOK001", 150 | quantity: 2, 151 | priceAtTime: 49.99, 152 | }, 153 | ], 154 | totalAmount: 1399.97, 155 | shippingAddress: { 156 | street: "123 Main St", 157 | city: "New York", 158 | country: "USA", 159 | }, 160 | paymentMethod: { 161 | type: "credit_card", 162 | last4: "4242", 163 | }, 164 | }, 165 | { 166 | _id: new ObjectId(), 167 | userId: userIds.jane, 168 | orderDate: new Date("2024-02-01"), 169 | status: "processing", 170 | items: [ 171 | { 172 | productSku: "PHONE001", 173 | quantity: 1, 174 | priceAtTime: 699.99, 175 | }, 176 | ], 177 | totalAmount: 699.99, 178 | shippingAddress: { 179 | street: "456 Market St", 180 | city: "San Francisco", 181 | country: "USA", 182 | }, 183 | paymentMethod: { 184 | type: "paypal", 185 | email: "jane@example.com", 186 | }, 187 | }, 188 | ]; 189 | 190 | await db.collection("orders").insertMany(orders); 191 | 192 | console.log("Seed completed successfully!"); 193 | } catch (error) { 194 | console.error("Seed failed:", error); 195 | } finally { 196 | await client.close(); 197 | } 198 | } 199 | 200 | seed(); 201 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | PRIVATE_README.md 76 | 77 | # dotenv environment variable files 78 | .env 79 | .env.development.local 80 | .env.test.local 81 | .env.production.local 82 | .env.local 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | .parcel-cache 87 | 88 | # Next.js build output 89 | .next 90 | out 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and not Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # vuepress v2.x temp and cache directory 106 | .temp 107 | .cache 108 | 109 | # Docusaurus cache and generated files 110 | .docusaurus 111 | 112 | # Serverless directories 113 | .serverless/ 114 | 115 | # FuseBox cache 116 | .fusebox/ 117 | 118 | # DynamoDB Local files 119 | .dynamodb/ 120 | 121 | # TernJS port file 122 | .tern-port 123 | 124 | # Stores VSCode versions used for testing VSCode extensions 125 | .vscode-test 126 | 127 | # yarn v2 128 | .yarn/cache 129 | .yarn/unplugged 130 | .yarn/build-state.yml 131 | .yarn/install-state.gz 132 | .pnp.* 133 | 134 | build/ 135 | 136 | gcp-oauth.keys.json 137 | .*-server-credentials.json 138 | 139 | # Byte-compiled / optimized / DLL files 140 | __pycache__/ 141 | *.py[cod] 142 | *$py.class 143 | 144 | # C extensions 145 | *.so 146 | 147 | # Distribution / packaging 148 | .Python 149 | build/ 150 | develop-eggs/ 151 | dist/ 152 | downloads/ 153 | eggs/ 154 | .eggs/ 155 | lib/ 156 | lib64/ 157 | parts/ 158 | sdist/ 159 | var/ 160 | wheels/ 161 | share/python-wheels/ 162 | *.egg-info/ 163 | .installed.cfg 164 | *.egg 165 | MANIFEST 166 | 167 | # PyInstaller 168 | # Usually these files are written by a python script from a template 169 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 170 | *.manifest 171 | *.spec 172 | 173 | # Installer logs 174 | pip-log.txt 175 | pip-delete-this-directory.txt 176 | 177 | # Unit test / coverage reports 178 | htmlcov/ 179 | .tox/ 180 | .nox/ 181 | .coverage 182 | .coverage.* 183 | .cache 184 | nosetests.xml 185 | coverage.xml 186 | *.cover 187 | *.py,cover 188 | .hypothesis/ 189 | .pytest_cache/ 190 | cover/ 191 | 192 | # Translations 193 | *.mo 194 | *.pot 195 | 196 | # Django stuff: 197 | *.log 198 | local_settings.py 199 | db.sqlite3 200 | db.sqlite3-journal 201 | 202 | # Flask stuff: 203 | instance/ 204 | .webassets-cache 205 | 206 | # Scrapy stuff: 207 | .scrapy 208 | 209 | # Sphinx documentation 210 | docs/_build/ 211 | 212 | # PyBuilder 213 | .pybuilder/ 214 | target/ 215 | 216 | # Jupyter Notebook 217 | .ipynb_checkpoints 218 | 219 | # IPython 220 | profile_default/ 221 | ipython_config.py 222 | 223 | # pyenv 224 | # For a library or package, you might want to ignore these files since the code is 225 | # intended to run in multiple environments; otherwise, check them in: 226 | # .python-version 227 | 228 | # pipenv 229 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 230 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 231 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 232 | # install all needed dependencies. 233 | #Pipfile.lock 234 | 235 | # poetry 236 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 237 | # This is especially recommended for binary packages to ensure reproducibility, and is more 238 | # commonly ignored for libraries. 239 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 240 | #poetry.lock 241 | 242 | # pdm 243 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 244 | #pdm.lock 245 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 246 | # in version control. 247 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 248 | .pdm.toml 249 | .pdm-python 250 | .pdm-build/ 251 | 252 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 253 | __pypackages__/ 254 | 255 | # Celery stuff 256 | celerybeat-schedule 257 | celerybeat.pid 258 | 259 | # SageMath parsed files 260 | *.sage.py 261 | 262 | # Environments 263 | .env 264 | .venv 265 | env/ 266 | venv/ 267 | ENV/ 268 | env.bak/ 269 | venv.bak/ 270 | 271 | # Spyder project settings 272 | .spyderproject 273 | .spyproject 274 | 275 | # Rope project settings 276 | .ropeproject 277 | 278 | # mkdocs documentation 279 | /site 280 | 281 | # mypy 282 | .mypy_cache/ 283 | .dmypy.json 284 | dmypy.json 285 | 286 | # Pyre type checker 287 | .pyre/ 288 | 289 | # pytype static type analyzer 290 | .pytype/ 291 | 292 | # Cython debug symbols 293 | cython_debug/ 294 | 295 | .DS_Store 296 | 297 | # PyCharm 298 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 299 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 300 | # and can be added to the global gitignore or merged into this file. For a more nuclear 301 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 302 | #.idea/ 303 | --------------------------------------------------------------------------------