#
tokens: 3529/50000 6/6 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   └── mongodb
│       └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# TypeScript
dist/
build/

# Environment variables
.env
.env.local
.env.*.local

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs/
*.log

# Coverage
coverage/

# Temporary files
tmp/
temp/ 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP Database Server

A Model Context Protocol (MCP) server implementation that allows Large Language Models (LLMs) to interact with various databases through natural language. Currently supports MongoDB, with plans to support:

- PostgreSQL
- CockroachDB
- Redis
- And more...

## Features

- Database operations through natural language
- Currently supports MongoDB with features:
  - List all collections
  - Query documents with filtering and projection
  - Insert documents
  - Delete documents
  - Aggregate pipeline operations
- Future support for other databases:
  - PostgreSQL: SQL queries, table operations
  - CockroachDB: Distributed SQL operations
  - Redis: Key-value operations, caching

## Prerequisites

- Node.js v20.12.2 or higher
- Database (currently MongoDB, other databases coming soon)
- Claude Desktop Application

## Installation

1. Clone the repository:

```bash
git clone https://github.com/manpreet2000/mcp-database-server.git
cd mcp-database-server
```

2. Install dependencies:

```bash
npm install
```

3. Build the TypeScript code:

```bash
npm run build
```

## Configuration

To get started, you need to configure your database connection in your Claude Desktop configuration file:

### MacOS

```bash
~/Library/Application\ Support/Claude/claude_desktop_config.json
```

### Windows

```bash
%APPDATA%/Claude/claude_desktop_config.json
```

Add the following configuration to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "database": {
      "command": "/path/to/node",
      "args": ["/path/to/mcp-database/dist/index.js"],
      "env": {
        "MONGODB_URI": "your-mongodb-connection-string"
      }
    }
  }
}
```

Replace:

- `/path/to/node` with your Node.js executable path or just use `node`
- `/path/to/mcp-database` with the absolute path to this repository
- `your-mongodb-connection-string` with your MongoDB connection URL

## Usage Examples

### MongoDB Examples

1. List all collections in your database:

```
Can you show me all the collections in my database?
```

2. Get specific records from a collection:

```
Give me 2 records from the chargers collection
```

3. Query with filters:

```
Show me all documents in the users collection where status is active
```

4. Insert a document:

```
Add a new user to the users collection with name John and email [email protected]
```

5. Delete a document:

```
Remove the user with email [email protected] from the users collection
```

6. Aggregate data:

```
Show me the total count of users by status in the users collection
```

## Available Tools

### 1. getCollections

Lists all collections in the connected database.

### 2. getCollection

Retrieves documents from a collection with optional query parameters:

- `collectionName`: Name of the collection
- `limit`: Maximum number of documents to return (default: 10, max: 1000)
- `query`: MongoDB query object
- `projection`: Fields to include/exclude

### 3. insertOne

Inserts a single document into a collection:

- `collectionName`: Name of the collection
- `document`: Document object to insert

### 4. deleteOne

Deletes a single document from a collection:

- `collectionName`: Name of the collection
- `query`: Query to match the document to delete

### 5. aggregate

Executes an aggregation pipeline:

- `collectionName`: Name of the collection
- `pipeline`: Array of aggregation stages
- `options`: Optional aggregation options

## Future Database Support

### PostgreSQL

- SQL query execution
- Table operations
- Schema management
- Transaction support

### CockroachDB

- Distributed SQL operations
- Multi-region support
- Transaction management
- Schema operations

### Redis

- Key-value operations
- Caching mechanisms
- Pub/sub operations
- Data structure operations

## Security

- Never commit your database connection strings to version control
- Use environment variables for sensitive information
- Follow database-specific security best practices

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## License

MIT License - See [LICENSE](LICENSE) for details

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

--------------------------------------------------------------------------------
/src/mongodb/index.ts:
--------------------------------------------------------------------------------

```typescript
import { MongoClient, Db } from "mongodb";

export class MongoDBConnection {
  private client: MongoClient | null = null;
  private db: Db | null = null;

  async connect(databaseUrl: string) {
    try {
      this.client = new MongoClient(databaseUrl);
      await this.client.connect();
      this.db = this.client.db();
      return this.db;
    } catch (error) {
      console.error("MongoDB connection error:", error);
      throw error;
    }
  }

  async close() {
    await this.client?.close();
  }

  getClient(): MongoClient | null {
    return this.client;
  }

  getDb(): Db | null {
    return this.db;
  }
}

export const mongodbConnection = new MongoDBConnection();

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "mcp-database",
  "version": "1.0.0",
  "description": "MCP Server for database operations",
  "main": "index.js",
  "bin": {
    "database": "./dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
    "prepare": "npm run build",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts",
    "watch": "tsc --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "mcp",
    "mongodb",
    "database"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.4.0",
    "dotenv": "^16.4.5",
    "mongodb": "^6.15.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as dotenv from "dotenv";
import { z } from "zod";
import { mongodbConnection } from "./mongodb/index.js";
dotenv.config();

class DatabaseMCPServer {
  private mcpServer: McpServer;
  private readonly MONGODB_URI: string;

  constructor() {
    const uri = process.env.MONGODB_URI;
    if (!uri) {
      throw new Error("MONGODB_URI environment variable is required");
    }
    this.MONGODB_URI = uri;
    this.mcpServer = new McpServer({
      name: "database-mcp",
      version: "1.0.0",
    });
    this.initializeTools();
  }

  private initializeTools() {
    this.mcpServer.tool("getCollections", async () => {
      try {
        let db = mongodbConnection.getDb();
        if (!db) {
          await mongodbConnection.connect(this.MONGODB_URI);
          db = mongodbConnection.getDb();
          if (!db) throw new Error("Failed to connect to database");
        }
        const collections = await db.listCollections().toArray();
        return {
          content: [
            { type: "text", text: collections.map((c) => c.name).join(", ") },
          ],
        };
      } catch (error) {
        console.error(error);
        return {
          content: [{ type: "text", text: "Error: " + error }],
        };
      }
    });

    this.mcpServer.tool(
      "getCollection",
      {
        collectionName: z.string(),
        limit: z.number().min(1).max(1000).optional().default(10),
        query: z.object({}).optional(),
        projection: z.object({}).optional(),
      },
      async ({
        collectionName,
        limit,
        query,
        projection,
      }: {
        collectionName: string;
        limit?: number;
        query?: any;
        projection?: any;
      }) => {
        try {
          let db = mongodbConnection.getDb();
          if (!db) {
            await mongodbConnection.connect(this.MONGODB_URI);
            db = mongodbConnection.getDb();
            if (!db) throw new Error("Failed to connect to database");
          }
          const collection = db.collection(collectionName);
          const documents = await collection
            .find(query ?? {})
            .limit(limit ?? 100)
            .project(projection ?? {})
            .toArray();
          return {
            content: [
              {
                type: "text",
                text: documents.map((d) => JSON.stringify(d)).join("\n"),
              },
            ],
          };
        } catch (error) {
          console.error(error);
          return {
            content: [{ type: "text", text: "Error: " + error }],
          };
        }
      }
    );

    this.mcpServer.tool(
      "insertOne",
      {
        collectionName: z.string(),
        document: z.object({}),
      },
      async ({ collectionName, document }) => {
        try {
          let db = mongodbConnection.getDb();
          if (!db) {
            await mongodbConnection.connect(this.MONGODB_URI);
            db = mongodbConnection.getDb();
            if (!db) throw new Error("Failed to connect to database");
          }
          const collection = db.collection(collectionName);
          await collection.insertOne(document);
          return {
            content: [{ type: "text", text: "Document inserted successfully" }],
          };
        } catch (error) {
          console.error(error);
          return {
            content: [{ type: "text", text: "Error: " + error }],
          };
        }
      }
    );
    this.mcpServer.tool(
      "deleteOne",
      {
        collectionName: z.string(),
        query: z.object({}),
      },
      async ({ collectionName, query }) => {
        try {
          let db = mongodbConnection.getDb();
          if (!db) {
            await mongodbConnection.connect(this.MONGODB_URI);
            db = mongodbConnection.getDb();
            if (!db) throw new Error("Failed to connect to database");
          }
          const collection = db.collection(collectionName);
          await collection.deleteOne(query);
          return {
            content: [{ type: "text", text: "Document deleted successfully" }],
          };
        } catch (error) {
          console.error(error);
          return {
            content: [{ type: "text", text: "Error: " + error }],
          };
        }
      }
    );
    this.mcpServer.tool(
      "aggregate",
      {
        collectionName: z.string(),
        pipeline: z.array(
          z
            .object({
              $match: z.record(z.any()).optional(),
              $group: z.record(z.any()).optional(),
              $project: z.record(z.any()).optional(),
              $sort: z.record(z.any()).optional(),
              $limit: z.number().optional(),
              $skip: z.number().optional(),
              $unwind: z.string().optional(),
              $lookup: z
                .object({
                  from: z.string(),
                  localField: z.string(),
                  foreignField: z.string(),
                  as: z.string(),
                })
                .optional(),
              $count: z.string().optional(),
              $addFields: z.record(z.any()).optional(),
              $replaceRoot: z.record(z.any()).optional(),
              $facet: z.record(z.any()).optional(),
              $bucket: z.record(z.any()).optional(),
              $geoNear: z.record(z.any()).optional(),
              $indexStats: z.record(z.any()).optional(),
              $listLocalSessions: z.record(z.any()).optional(),
              $listSessions: z.record(z.any()).optional(),
              $merge: z.record(z.any()).optional(),
              $out: z.string().optional(),
              $planCacheStats: z.record(z.any()).optional(),
              $redact: z.record(z.any()).optional(),
              $replaceWith: z.record(z.any()).optional(),
              $sample: z.object({ size: z.number() }).optional(),
              $search: z.record(z.any()).optional(),
              $searchMeta: z.record(z.any()).optional(),
              $set: z.record(z.any()).optional(),
              $setWindowFields: z.record(z.any()).optional(),
              $unionWith: z.record(z.any()).optional(),
              $unset: z.string().optional(),
            })
            .refine(
              (obj) => {
                // Count the number of defined fields
                const definedFields = Object.keys(obj).filter(
                  (key) => obj[key as keyof typeof obj] !== undefined
                );
                return definedFields.length === 1;
              },
              {
                message: "Each pipeline stage must contain exactly one field",
              }
            )
        ),
        options: z.object({}).optional(),
      },
      async ({ collectionName, pipeline, options }) => {
        try {
          let db = mongodbConnection.getDb();
          if (!db) {
            await mongodbConnection.connect(this.MONGODB_URI);
            db = mongodbConnection.getDb();
            if (!db) throw new Error("Failed to connect to database");
          }
          const collection = db.collection(collectionName);
          const result = await collection
            .aggregate(pipeline, { maxTimeMS: 30000, ...options })
            .toArray();
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(result),
              },
            ],
          };
        } catch (error) {
          console.error(error);
          return {
            content: [{ type: "text", text: "Error: " + error }],
          };
        }
      }
    );
  }

  async connect(transport: StdioServerTransport) {
    await this.mcpServer.connect(transport);
  }

  async close() {
    await mongodbConnection.close();
  }
}

async function main() {
  const server = new DatabaseMCPServer();
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.log("Database MCP Server running on stdio");
}

process.on("SIGINT", async () => {
  try {
    console.error("SIGINT received");
    await mongodbConnection.close();
  } finally {
    process.exit(0);
  }
});

process.on("unhandledRejection", (error) => {
  console.error("Unhandled promise rejection:", error);
  process.exit(1);
});

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

```