server.js•10.5 kB
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
InitializeRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ListPromptsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { MongoClient } from "mongodb";
import { readFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { allTools, findMasterUserByPlatformId, findMasterUserByEmail, findMasterUserById, findMasterUserByAccountId, findInvoicesByAccountId, findInvoiceById } from "./tools/index.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Parse command line arguments
const args = process.argv.slice(2);
let mongoUri = null;
let databaseName = null;
// Parse --mongo-uri and --database arguments
for (let i = 0; i < args.length; i++) {
if ((args[i] === '--mongo-uri' || args[i] === '--uri') && i + 1 < args.length) {
mongoUri = args[i + 1];
} else if ((args[i] === '--database' || args[i] === '--db') && i + 1 < args.length) {
databaseName = args[i + 1];
}
// Support single argument format: mongodb://uri/database
else if (args[i].startsWith('mongodb://') || args[i].startsWith('mongodb+srv://')) {
// If URI contains database name, extract it
const url = new URL(args[i]);
if (url.pathname && url.pathname !== '/') {
mongoUri = args[i].split('/').slice(0, -1).join('/');
databaseName = url.pathname.substring(1);
} else {
mongoUri = args[i];
}
}
}
// Check environment variables
mongoUri = mongoUri || process.env.MONGODB_URI;
databaseName = databaseName || process.env.MONGODB_DATABASE;
// Fallback to settings.json
let settings = {};
if (!mongoUri || !databaseName) {
try {
const settingsPath = join(__dirname, "settings.json");
const settingsContent = readFileSync(settingsPath, "utf8");
settings = JSON.parse(settingsContent);
} catch (error) {
console.error("Error reading settings.json:", error.message);
console.error("Using default settings...");
settings = {
mongodb: {
uri: "mongodb://localhost:27017",
database: "mcpserver"
}
};
}
}
// Final configuration with priority: CLI args > env vars > settings.json > defaults
const MONGODB_URI = mongoUri || settings.mongodb?.uri || "mongodb://localhost:27017";
const DATABASE_NAME = databaseName || settings.mongodb?.database || "mcpserver";
console.error(`MongoDB configuration: URI=${MONGODB_URI}, Database=${DATABASE_NAME}`);
class MCPMongoServer {
constructor() {
this.server = new Server(
{
name: "simple-mcp-mongodb-server",
version: "1.0.0",
},
{
capabilities: {
tools: {
listChanged: true,
},
resources: {},
prompts: {},
},
}
);
this.mongoClient = null;
this.db = null;
this.isConnected = false;
this.setupHandlers();
}
setupHandlers() {
this.server.setRequestHandler(InitializeRequestSchema, async () => {
console.error("MCP client connected and initialized");
return {
protocolVersion: "2024-11-05",
capabilities: {
tools: {
listChanged: true,
},
resources: {},
prompts: {},
},
serverInfo: {
name: "simple-mcp-mongodb-server",
version: "1.0.0",
},
};
});
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "check_connection",
description: "Check if MongoDB connection is active and healthy",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
...allTools,
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
console.error(`Received tool request: ${name}`);
// Connect to MongoDB if not already connected for any tool
if (!this.isConnected || !this.mongoClient) {
console.error("Not connected to MongoDB, attempting connection...");
await this.connectToMongoDB();
}
try {
switch (name) {
case "check_connection":
return await this.checkConnection();
case "find-master-user-by-platform-id":
const usersByPlatformId = await findMasterUserByPlatformId(args.platformId, args.limit);
return {
content: [
{
type: "text",
text: JSON.stringify(usersByPlatformId, null, 2),
},
],
};
case "find-master-user-by-email":
const usersByEmail = await findMasterUserByEmail(args.email, args.limit);
return {
content: [
{
type: "text",
text: JSON.stringify(usersByEmail, null, 2),
},
],
};
case "find-master-user-by-id":
const userById = await findMasterUserById(args.id);
return {
content: [
{
type: "text",
text: JSON.stringify(userById, null, 2),
},
],
};
case "find-master-user-by-account-id":
const usersByAccountId = await findMasterUserByAccountId(args.accountId, args.limit);
return {
content: [
{
type: "text",
text: JSON.stringify(usersByAccountId, null, 2),
},
],
};
case "find-invoices-by-account-id":
const invoicesByAccountId = await findInvoicesByAccountId(args.accountId, args.limit);
return {
content: [
{
type: "text",
text: JSON.stringify(invoicesByAccountId, null, 2),
},
],
};
case "find-invoice-by-id":
const invoiceById = await findInvoiceById(args.id);
return {
content: [
{
type: "text",
text: JSON.stringify(invoiceById, null, 2),
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
return {
content: [
{
type: "text",
text: `Error executing tool ${name}: ${error.message}`,
},
],
isError: true,
};
}
});
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [],
};
});
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [],
};
});
}
async connectToMongoDB() {
try {
console.error("Connecting to MongoDB...");
this.mongoClient = new MongoClient(MONGODB_URI);
await this.mongoClient.connect();
this.db = this.mongoClient.db(DATABASE_NAME);
await this.db.admin().ping();
this.isConnected = true;
console.error(`Successfully connected to MongoDB at ${MONGODB_URI}`);
console.error(`Using database: ${DATABASE_NAME}`);
} catch (error) {
this.isConnected = false;
this.mongoClient = null;
this.db = null;
console.error("Failed to connect to MongoDB:", error.message);
console.error("Server will continue running without MongoDB connection");
}
}
async checkConnection() {
const timeout = (ms) => new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timed out')), ms)
);
try {
if (!this.mongoClient || !this.db || !this.isConnected) {
return {
content: [
{
type: "text",
text: `MongoDB not available\nTarget URI: ${MONGODB_URI}\nTarget Database: ${DATABASE_NAME}\nStatus: Disconnected - MCP server is running without database connection`,
},
],
};
}
// Apply 10-second timeout to ping and serverStatus operations
await Promise.race([
this.db.admin().ping(),
timeout(10000)
]);
const serverStatus = await Promise.race([
this.db.admin().serverStatus(),
timeout(10000)
]);
return {
content: [
{
type: "text",
text: `MongoDB connection is healthy\nConnected to: ${MONGODB_URI}\nDatabase: ${DATABASE_NAME}\nServer version: ${serverStatus.version}\nUptime: ${serverStatus.uptime} seconds`,
},
],
};
} catch (error) {
this.isConnected = false;
this.mongoClient = null;
this.db = null;
return {
content: [
{
type: "text",
text: `MongoDB connection lost: ${error.message}\nTarget URI: ${MONGODB_URI}\nTarget Database: ${DATABASE_NAME}\nStatus: MCP server continues running without database connection`,
},
],
};
}
}
async cleanup() {
if (this.mongoClient) {
try {
await this.mongoClient.close();
this.isConnected = false;
console.error("MongoDB connection closed");
} catch (error) {
console.error("Error closing MongoDB connection:", error.message);
}
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("MCP MongoDB Server started and ready for connections");
// Keep the server running indefinitely
process.stdin.resume();
}
}
const server = new MCPMongoServer();
process.on("SIGINT", async () => {
console.error("Received SIGINT, shutting down gracefully...");
await server.cleanup();
process.exit(0);
});
process.on("SIGTERM", async () => {
console.error("Received SIGTERM, shutting down gracefully...");
await server.cleanup();
process.exit(0);
});
server.run().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});