Skip to main content
Glama

MongoDB MCP Server

Official
by mongodb-js
server.ts13.1 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { Session } from "./common/session.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { AtlasTools } from "./tools/atlas/tools.js"; import { AtlasLocalTools } from "./tools/atlasLocal/tools.js"; import { MongoDbTools } from "./tools/mongodb/tools.js"; import { Resources } from "./resources/resources.js"; import type { LogLevel } from "./common/logger.js"; import { LogId, McpLogger } from "./common/logger.js"; import type { Telemetry } from "./telemetry/telemetry.js"; import type { UserConfig } from "./common/config.js"; import { type ServerEvent } from "./telemetry/types.js"; import { type ServerCommand } from "./telemetry/types.js"; import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { CallToolRequestSchema, SetLevelRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import assert from "assert"; import type { ToolBase, ToolCategory, ToolConstructorParams } from "./tools/tool.js"; import { validateConnectionString } from "./helpers/connectionOptions.js"; import { packageInfo } from "./common/packageInfo.js"; import { type ConnectionErrorHandler } from "./common/connectionErrorHandler.js"; import type { Elicitation } from "./elicitation.js"; export interface ServerOptions { session: Session; userConfig: UserConfig; mcpServer: McpServer; telemetry: Telemetry; elicitation: Elicitation; connectionErrorHandler: ConnectionErrorHandler; toolConstructors?: (new (params: ToolConstructorParams) => ToolBase)[]; } export class Server { public readonly session: Session; public readonly mcpServer: McpServer; private readonly telemetry: Telemetry; public readonly userConfig: UserConfig; public readonly elicitation: Elicitation; private readonly toolConstructors: (new (params: ToolConstructorParams) => ToolBase)[]; public readonly tools: ToolBase[] = []; public readonly connectionErrorHandler: ConnectionErrorHandler; private _mcpLogLevel: LogLevel = "debug"; public get mcpLogLevel(): LogLevel { return this._mcpLogLevel; } private readonly startTime: number; private readonly subscriptions = new Set<string>(); constructor({ session, mcpServer, userConfig, telemetry, connectionErrorHandler, elicitation, toolConstructors, }: ServerOptions) { this.startTime = Date.now(); this.session = session; this.telemetry = telemetry; this.mcpServer = mcpServer; this.userConfig = userConfig; this.elicitation = elicitation; this.connectionErrorHandler = connectionErrorHandler; this.toolConstructors = toolConstructors ?? [...AtlasTools, ...MongoDbTools, ...AtlasLocalTools]; } async connect(transport: Transport): Promise<void> { await this.validateConfig(); // Register resources after the server is initialized so they can listen to events like // connection events. this.registerResources(); this.mcpServer.server.registerCapabilities({ logging: {}, resources: { listChanged: true, subscribe: true }, instructions: this.getInstructions(), }); // TODO: Eventually we might want to make tools reactive too instead of relying on custom logic. this.registerTools(); // This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments` // object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if // the tool accepts any arguments, even if they're all optional. // // see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705 // Since paramsSchema here is not undefined, the server will create a non-optional z.object from it. const existingHandler = ( this.mcpServer.server["_requestHandlers"] as Map< string, (request: unknown, extra: unknown) => Promise<CallToolResult> > ).get(CallToolRequestSchema.shape.method.value); assert(existingHandler, "No existing handler found for CallToolRequestSchema"); this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => { if (!request.params.arguments) { request.params.arguments = {}; } return existingHandler(request, extra); }); this.mcpServer.server.setRequestHandler(SubscribeRequestSchema, ({ params }) => { this.subscriptions.add(params.uri); this.session.logger.debug({ id: LogId.serverInitialized, context: "resources", message: `Client subscribed to resource: ${params.uri}`, }); return {}; }); this.mcpServer.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => { this.subscriptions.delete(params.uri); this.session.logger.debug({ id: LogId.serverInitialized, context: "resources", message: `Client unsubscribed from resource: ${params.uri}`, }); return {}; }); this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, ({ params }) => { if (!McpLogger.LOG_LEVELS.includes(params.level)) { throw new Error(`Invalid log level: ${params.level}`); } this._mcpLogLevel = params.level; return {}; }); this.mcpServer.server.oninitialized = (): void => { this.session.setMcpClient(this.mcpServer.server.getClientVersion()); // Placed here to start the connection to the config connection string as soon as the server is initialized. void this.connectToConfigConnectionString(); this.session.logger.info({ id: LogId.serverInitialized, context: "server", message: `Server with version ${packageInfo.version} started with transport ${transport.constructor.name} and agent runner ${JSON.stringify(this.session.mcpClient)}`, }); this.emitServerTelemetryEvent("start", Date.now() - this.startTime); }; this.mcpServer.server.onclose = (): void => { const closeTime = Date.now(); this.emitServerTelemetryEvent("stop", Date.now() - closeTime); }; this.mcpServer.server.onerror = (error: Error): void => { const closeTime = Date.now(); this.emitServerTelemetryEvent("stop", Date.now() - closeTime, error); }; await this.mcpServer.connect(transport); } async close(): Promise<void> { await this.telemetry.close(); await this.session.close(); await this.mcpServer.close(); } public sendResourceListChanged(): void { this.mcpServer.sendResourceListChanged(); } public isToolCategoryAvailable(name: ToolCategory): boolean { return !!this.tools.filter((t) => t.category === name).length; } public sendResourceUpdated(uri: string): void { this.session.logger.info({ id: LogId.resourceUpdateFailure, context: "resources", message: `Resource updated: ${uri}`, }); if (this.subscriptions.has(uri)) { void this.mcpServer.server.sendResourceUpdated({ uri }); } } private emitServerTelemetryEvent(command: ServerCommand, commandDuration: number, error?: Error): void { const event: ServerEvent = { timestamp: new Date().toISOString(), source: "mdbmcp", properties: { result: "success", duration_ms: commandDuration, component: "server", category: "other", command: command, }, }; if (command === "start") { event.properties.startup_time_ms = commandDuration; event.properties.read_only_mode = this.userConfig.readOnly ? "true" : "false"; event.properties.disabled_tools = this.userConfig.disabledTools || []; event.properties.confirmation_required_tools = this.userConfig.confirmationRequiredTools || []; } if (command === "stop") { event.properties.runtime_duration_ms = Date.now() - this.startTime; if (error) { event.properties.result = "failure"; event.properties.reason = error.message; } } this.telemetry.emitEvents([event]); } private registerTools(): void { for (const toolConstructor of this.toolConstructors) { const tool = new toolConstructor({ session: this.session, config: this.userConfig, telemetry: this.telemetry, elicitation: this.elicitation, }); if (tool.register(this)) { this.tools.push(tool); } } } private registerResources(): void { for (const resourceConstructor of Resources) { const resource = new resourceConstructor(this.session, this.userConfig, this.telemetry); resource.register(this); } } private async validateConfig(): Promise<void> { // Validate connection string if (this.userConfig.connectionString) { try { validateConnectionString(this.userConfig.connectionString, false); } catch (error) { console.error("Connection string validation failed with error: ", error); throw new Error( "Connection string validation failed with error: " + (error instanceof Error ? error.message : String(error)) ); } } // Validate API client credentials if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) { try { if (!this.userConfig.apiBaseUrl.startsWith("https://")) { const message = "Failed to validate MongoDB Atlas the credentials from config: apiBaseUrl must start with https://"; console.error(message); throw new Error(message); } await this.session.apiClient.validateAccessToken(); } catch (error) { if (this.userConfig.connectionString === undefined) { console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error); throw new Error( "Failed to connect to MongoDB Atlas instance using the credentials from the config" ); } console.error( "Failed to validate MongoDB Atlas the credentials from the config, but validated the connection string." ); } } } private getInstructions(): string { let instructions = ` This is the MongoDB MCP server. `; if (this.userConfig.connectionString) { instructions += ` This MCP server was configured with a MongoDB connection string, and you can assume that you are connected to a MongoDB cluster. `; } if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) { instructions += ` This MCP server was configured with MongoDB Atlas API credentials.`; } return instructions; } private async connectToConfigConnectionString(): Promise<void> { if (this.userConfig.connectionString) { try { this.session.logger.info({ id: LogId.mongodbConnectTry, context: "server", message: `Detected a MongoDB connection string in the configuration, trying to connect...`, }); await this.session.connectToMongoDB({ connectionString: this.userConfig.connectionString, }); } catch (error) { // We don't throw an error here because we want to allow the server to start even if the connection string is invalid. this.session.logger.error({ id: LogId.mongodbConnectFailure, context: "server", message: `Failed to connect to MongoDB instance using the connection string from the config: ${error instanceof Error ? error.message : String(error)}`, }); } } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mongodb-js/mongodb-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server