This guide explains how to embed and extend the MongoDB MCP Server as a library to customize its core functionality and behavior for your specific use cases. It documents the v3 API: the monorepo of scoped @mongodb-js/mcp-* packages.
Migrating from the pre-v3 single-package API? The
mongodb-mcp-serverpackage is not a library in v3 — see the v1 → v3 migration guide (in the repository) for how to update consumer code.
In v3 the MongoDB MCP Server is a monorepo of scoped packages under the @mongodb-js/mcp-* naming. The mongodb-mcp-server package itself is now a binary-only distribution (npx mongodb-mcp-server / the MCPB bundle) — it is not an importable library.
To embed or extend the server, depend on the scoped packages instead. The library exports provide full control over:
- Server configuration and initialization —
runMcpCli,createRunnerFromConfig,createSharedServicesFromConfig+createServerFromConfig,startRunner - Per-session (MCP Client session) configuration hooks —
MCPHttpServer.createServerForRequest - Tool registration —
ToolBase/ToolClasstool classes andToolRegistryarrays - Connection management and connection error handling —
MCPConnectionManager,connectionErrorHandler
Install only the scoped packages your embedding needs (see the use cases below):
# Custom CLI (most common embedding)
npm install @mongodb-js/mcp-cli @mongodb-js/mcp-tools-mongodb @mongodb-js/mcp-tools-atlas
# Custom tools
npm install @mongodb-js/mcp-core @mongodb-js/mcp-types
# HTTP host
npm install @mongodb-js/mcp-cli @mongodb-js/mcp-core @mongodb-js/mcp-http-runnersAll packages are available as ES modules. The server targets Node.js >= 24.
| Package | Role |
|---|---|
@mongodb-js/mcp-cli |
Primary entry point. Custom CLI (runMcpCli), server+session classes (CliServer, Session), config (parseUserConfig, UserConfigSchema, configRegistry, applyConfigOverrides), create*FromConfig factories, Resources, CLI handlers |
@mongodb-js/mcp-core |
Transports (StdioRunner, InMemoryTransport), SessionStore, Keychain, Elicitation, NoopLogger, NoopTelemetry, tool base classes (ToolBase, ToolClass) |
@mongodb-js/mcp-http-runners |
HTTP transport (StreamableHttpRunner, MCPHttpServer, MonitoringServer) |
@mongodb-js/mcp-types |
Shared types (ServerMetadata, TransportRequestContext, ToolCategory, OperationType, UserConfig, …) |
@mongodb-js/mcp-tools-* |
Tool bundles: @mongodb-js/mcp-tools-mongodb, -atlas, -atlas-local, -assistant |
@mongodb-js/mcp-atlas-api-client |
Atlas Admin API client (ApiClient, ClientCredentialsAuthProvider) |
@mongodb-js/mcp-atlas-telemetry |
Telemetry pipeline (AtlasTelemetry) |
@mongodb-js/mcp-logging |
Loggers (ConsoleLogger, DiskLogger, McpLogger) |
@mongodb-js/mcp-metrics |
Metrics (PrometheusMetrics, createDefaultMetrics) |
@mongodb-js/mcp-ui |
MCP UI registry (UIRegistry) |
There are three main approaches:
runMcpCli(recommended for CLIs): one call that parses config, runs handlers, creates the server and infrastructure, and starts stdio or HTTP transport — the same flow the official binary uses.createSharedServicesFromConfig+createRunnerFromConfig+startRunner: split the same flow so you can replace individual dependencies (logger, API client, telemetry, monitoring server) viacreate*FromConfigfactories, or create just the server (createServerFromConfig) and wire a custom runner.createSharedServicesFromConfig({ config, serverMetadata, tools, resources, logger })builds the app-level infrastructure shared by all servers (metrics,monitoringServer,keychain,deviceId,connectionStore,atlasLocalClient).createServerFromConfig({ config, sharedServices })builds one server (session-scoped state) from a resolved config.createRunnerFromConfigbuilds the shared infrastructure and returns only the configured transport runner (StdioRunnerfor stdio,StreamableHttpRunnerfor HTTP).startRunner({ transportRunner, logger, onExit })starts the runner and manages the server lifecycle (signal handlers, graceful shutdown).
- Override
MCPHttpServer.createServerForRequest: when hosting over HTTP and you need per-request (per-session) customization, subclassMCPHttpServerand override itscreateServerForRequest(request: TransportRequestContext)instead. In v3 this hook lives onMCPHttpServer, not onStreamableHttpRunner.
CliServer and the telemetry pipeline require a ServerMetadata value — the product name/version reported to clients and used for telemetry and driver appName:
import type { ServerMetadata } from "@mongodb-js/mcp-types";
const serverMetadata: ServerMetadata = {
mcpServerName: "my-product-mcp",
version: "1.0.0",
engines: { node: process.version },
};Prefer reading version/name from your package.json at build time when possible.
The MongoDB MCP Server library follows a modular architecture:
- Transport runners:
StdioRunner(stdio) andStreamableHttpRunner(HTTP) manage the MCP transport layer. Runners attach a pre-built server — they no longer build one for you. CliServer: wraps the MCP server and registers tools and resources; created per session.Session: per-client (MCP Client) connection and configuration state, includingsession.config(the effectiveUserConfig).- Tools: individual capabilities exposed to the MCP client, implemented as
ToolBasesubclasses and grouped into bundle arrays (MongoDBTools,AtlasTools, …). - Configuration:
UserConfigparsed viaparseUserConfig/UserConfigSchema, with request-level override mechanisms (applyConfigOverrides,configRegistry).
Configure the MCP server with custom settings, such as HTTP headers for authentication before establishing a session for an MCP client, or replace parts of the default infrastructure.
import {
createLoggerFromConfig,
createRunnerFromConfig,
startRunner,
parseUserConfig,
} from "@mongodb-js/mcp-cli";
import { MongoDBTools } from "@mongodb-js/mcp-tools-mongodb";
import { Resources } from "@mongodb-js/mcp-cli";
import { Keychain } from "@mongodb-js/mcp-core";
import type { ServerMetadata } from "@mongodb-js/mcp-types";
const { parsed: config } = parseUserConfig({
args: process.argv.slice(2),
});
const serverMetadata: ServerMetadata = {
mcpServerName: "my-product-mcp",
version: "1.0.0",
engines: { node: process.version },
};
const logger = await createLoggerFromConfig({
config,
keychain: Keychain.root,
});
const transportRunner = await createRunnerFromConfig({
config: {
...config,
httpHeaders: {
"x-api-key": "your-secret-api-key",
},
},
serverMetadata,
tools: [...MongoDBTools],
resources: Resources,
logger,
});
await startRunner({
transportRunner,
logger,
onExit: (code) => process.exit(code),
});Clients connecting to this server must include the specified headers in their requests, otherwise their session initialization request is declined.
Use individual create*FromConfig factories to swap dependencies:
import {
createLoggerFromConfig,
createApiClientFromConfig,
} from "@mongodb-js/mcp-cli";
import { Keychain } from "@mongodb-js/mcp-core";
const keychain = Keychain.root;
const logger = await createLoggerFromConfig({ config, keychain });
const apiClient = createApiClientFromConfig({ config, serverMetadata, logger });Available factories: createLoggerFromConfig, createApiClientFromConfig, createExportsManagerFromConfig, createTelemetryFromConfig, createMonitoringServerFromConfig.
Customize the server for each MCP client session — enabling user-specific permissions and settings based on request headers, query parameters, or authentication context — by subclassing MCPHttpServer and overriding createServerForRequest(request: TransportRequestContext).
The v1 pattern of overriding
createServerForRequestonStreamableHttpRunneris removed in v3. Runners no longer create servers.
import {
MCPHttpServer,
StreamableHttpRunner,
} from "@mongodb-js/mcp-http-runners";
import {
parseUserConfig,
createLoggerFromConfig,
createApiClientFromConfig,
createExportsManagerFromConfig,
Session,
type McpSession,
} from "@mongodb-js/mcp-cli";
import {
SessionStore,
Keychain,
Elicitation,
NoopTelemetry,
McpServer,
getRandomUUID,
} from "@mongodb-js/mcp-core";
import {
MCPConnectionStore,
MongoDBTools,
connectionErrorHandler,
DeviceId,
type ConnectionRegistry,
} from "@mongodb-js/mcp-tools-mongodb";
import { createDefaultMetrics } from "@mongodb-js/mcp-metrics";
import type {
TransportRequestContext,
ServerMetadata,
UserConfig,
} from "@mongodb-js/mcp-types";
interface UserPermissions {
role: "admin" | "developer" | "analyst";
allowedOperations: ("read" | "metadata" | "create" | "update" | "delete")[];
maxDocuments: number;
}
async function getUserPermissions(userId: string): Promise<UserPermissions> {
// Replace with your auth logic
return {
role: "analyst",
allowedOperations: ["read", "metadata"],
maxDocuments: 100,
};
}
const serverMetadata: ServerMetadata = {
mcpServerName: "my-product-mcp",
version: "1.0.0",
engines: { node: process.version },
};
// Shared infrastructure, built once (from the base config)
const { parsed: baseConfig } = parseUserConfig({ args: process.argv.slice(2) });
const keychain = Keychain.root;
const logger = await createLoggerFromConfig({ config: baseConfig, keychain });
const apiClient = createApiClientFromConfig({
config: baseConfig,
serverMetadata,
logger,
});
const exportsManager = createExportsManagerFromConfig({ config: baseConfig });
const metrics = createDefaultMetrics();
const deviceId = DeviceId.create(logger);
const connectionStore = new MCPConnectionStore({
options: baseConfig,
logger,
deviceId,
});
const connectionRegistry: ConnectionRegistry = connectionStore.view({
scope: baseConfig.connectionScope === "session" ? getRandomUUID() : undefined,
owned: true,
});
// Per-session factory: build a Session + CliServer from a per-request UserConfig
function createServerFromConfig(config: UserConfig): {
session: McpSession;
server: CliServer;
} {
const mcpServer = new McpServer({
name: serverMetadata.mcpServerName,
version: serverMetadata.version,
});
const session = new Session({
logger,
exportsManager,
connectionRegistry,
keychain,
connectionErrorHandler,
apiClient,
config,
});
return {
session,
server: new CliServer({
session,
mcpServer,
telemetry: new NoopTelemetry(),
elicitation: new Elicitation({
server: mcpServer.server,
timeoutMs: config.elicitationTimeoutMs ?? 30_000,
}),
connectionErrorHandler,
metrics,
serverMetadata,
tools: MongoDBTools,
}),
};
}
class PermissionsMCPHttpServer extends MCPHttpServer {
protected override async createServerForRequest(
request: TransportRequestContext
): Promise<CliServer> {
const userId = request?.headers?.["x-user-id"];
if (typeof userId !== "string") {
throw new Error("User authentication required: x-user-id header missing");
}
const permissions = await getUserPermissions(userId);
const allOperations = [
"read",
"metadata",
"create",
"update",
"delete",
"connect",
];
const disabledTools = allOperations.filter(
(op) => !permissions.allowedOperations.includes(op)
);
return createServerFromConfig({
...baseConfig,
disabledTools,
readOnly: permissions.role === "analyst",
maxDocumentsPerQuery: permissions.maxDocuments,
}).server;
}
}
const sessionStore = new SessionStore({
options: {
idleTimeoutMs: baseConfig.idleTimeoutMs,
notificationTimeoutMs: baseConfig.notificationTimeoutMs,
},
logger,
metrics,
});
const mcpHttpServer = new PermissionsMCPHttpServer({
options: {
http: {
host: baseConfig.httpHost,
port: baseConfig.httpPort,
bodyLimit: baseConfig.httpBodyLimit,
headers: baseConfig.httpHeaders,
responseType: baseConfig.httpResponseType,
},
session: {
idleTimeoutMs: baseConfig.idleTimeoutMs,
notificationTimeoutMs: baseConfig.notificationTimeoutMs,
externallyManagedSessions: baseConfig.externallyManagedSessions,
},
},
logger,
metrics,
sessionStore,
});
const runner = new StreamableHttpRunner({
logger,
metrics,
mcpHttpServer,
sessionStore,
});
await runner.start();Note: In this example
deviceId,connectionErrorHandler,MongoDBTools, andMCPConnectionStorecome from@mongodb-js/mcp-tools-mongodb(see Connection management); a real embedding typically wires the shared infrastructure once (ascreateServerFromConfigdoes) and builds only theSession/CliServerper request. MongoDB connection state deliberately lives at the app level (ConnectionRegistry), not in the session — tools address connections byconnectionId.
### Use Case 3: Adding Custom Tools
Implement custom tools by extending `ToolBase` from `@mongodb-js/mcp-core`:
```typescript
import { ToolBase, type ToolClass, type ToolCategory, type OperationType } from "@mongodb-js/mcp-core";
import type { IToolSession } from "@mongodb-js/mcp-types";
import { z } from "zod";
class MyCustomTool extends ToolBase<IToolSession> {
static toolName = "my-custom-tool";
static category: ToolCategory = "custom";
static operationType: OperationType = "read";
public description = "My custom tool description";
public argsShape = {
query: z.string().describe("The query parameter"),
};
protected async execute(args) {
// Tool implementation — arguments are inferred from argsShape
return {
content: [{ type: "text", text: "Result" }],
structuredContent: { query: args.query },
};
}
protected resolveTelemetryMetadata() {
return {};
}
}
Register the class by including it in the tools array (a ToolRegistry) passed to runMcpCli, createRunnerFromConfig, createServerFromConfig, or CliServer: const tools: ToolRegistry = [...MongoDBTools, MyCustomTool];.
Tool classes must conform to ToolClass — static toolName (unique), category ("mongodb" | "atlas" | "atlas-local" | "assistant" | "custom"), and operationType. The server injects session, telemetry, and elicitation automatically via the ToolConstructorParams. Use formatUntrustedData (from @mongodb-js/mcp-core) to format arbitrary data in tool output, and Elicitation (from @mongodb-js/mcp-core) to request user confirmation.
The built-in tools are exported as arrays per category. Select or filter them freely:
import { MongoDBTools } from "@mongodb-js/mcp-tools-mongodb";
import { AtlasTools } from "@mongodb-js/mcp-tools-atlas";
import { AtlasLocalTools } from "@mongodb-js/mcp-tools-atlas-local";
import { AssistantTools } from "@mongodb-js/mcp-tools-assistant";
// Only MongoDB read and metadata tools
const readOnlyTools = MongoDBTools.filter(
(Tool) => Tool.operationType === "read" || Tool.operationType === "metadata"
);
// Only atlas tools
const tools = [...AtlasTools];
// Standard bundle, no assistant
const standard = [...MongoDBTools, ...AtlasTools, ...AtlasLocalTools];Tool.operationType and Tool.category are static properties on each tool class, so filtering by them is type-safe.
| Symbol | Description |
|---|---|
runMcpCli({ args, serverMetadata, consoleLogger, onExit, tools, resources, handlers? }) |
Run the full CLI: parse config → handlers → create infrastructure → start server |
CliServer / CliServerOptions |
Core server wrapping the MCP server; created per session |
Session / SessionOptions |
Per-client session with session.config (effective UserConfig) |
parseUserConfig({ args }) |
Parse CLI args/env into { error, warnings, parsed } |
UserConfigSchema, configRegistry, ALL_CONFIG_KEYS |
Config schema and registry |
applyConfigOverrides, getConfigMeta, nameToConfigKey |
Request-level config overrides (HTTP headers / query params) |
createRunnerFromConfig({ config, serverMetadata, tools, resources, logger }) |
Build the transport runner only (StdioRunner or StreamableHttpRunner) |
createHttpTransportRunnerFromConfig(sharedServices) |
Build the HTTP transport runner (creates a fresh server per request) |
createSharedServicesFromConfig({ config, serverMetadata, tools, resources, logger }) |
Build app-level infra shared by all servers (metrics, connection store, ...) |
createServerFromConfig({ config, sharedServices }) |
Build one server (session-scoped state) from a resolved config |
CliMcpHttpServer |
HTTP server creating a fresh CliServer per session |
startRunner({ transportRunner, logger, onExit }) |
Start the runner and manage graceful shutdown |
createLoggerFromConfig / createApiClientFromConfig / createExportsManagerFromConfig / createTelemetryFromConfig / createMonitoringServerFromConfig |
Individual infrastructure factories |
Resources, ConfigResource, DebugResource, ExportedData |
Built-in MCP resources |
HelpHandler, VersionHandler, DryRunHandler |
CLI handlers |
| Types | ToolRegistry, ResourceRegistry, McpSession, RunMcpCliOptions |
| Symbol | Description |
|---|---|
ToolBase, ToolClass, ToolConstructorParams, ToolArgs, ToolResult, formatUntrustedData |
Custom tool authoring |
StdioRunner({ logger, server }) |
Stdio transport runner |
InMemoryTransport |
In-memory transport for tests |
SessionStore, createDefaultSessionStore |
HTTP session store |
Keychain, registerGlobalSecretToRedact, redactValues |
Secret storage/redaction |
Elicitation |
User confirmation requests |
NoopLogger, NoopTelemetry, LoggerBase, CompositeLogger |
Logging/telemetry primitives |
McpServer (re-export) |
@modelcontextprotocol/sdk server |
| Symbol | Description |
|---|---|
StreamableHttpRunner / StreamableHttpRunnerOptions |
HTTP transport runner |
MCPHttpServer / MCPHttpServerOptions |
HTTP server; override abstract createServerForRequest(request: TransportRequestContext): Promise<TServer> |
MonitoringServer / MonitoringServerOptions |
Optional /metrics monitoring server |
ExpressBasedHttpServer |
Base class for Express-based HTTP servers |
| Package | Symbols |
|---|---|
@mongodb-js/mcp-tools-mongodb |
MongoDBTools, MongoDBToolBase, MCPConnectionManager, ConnectionManager, ErrorCodes, MongoDBError, exports manager & connection types |
@mongodb-js/mcp-tools-atlas |
AtlasTools, AtlasToolBase |
@mongodb-js/mcp-tools-atlas-local |
AtlasLocalTools, createAtlasLocalClient |
@mongodb-js/mcp-tools-assistant |
AssistantTools |
@mongodb-js/mcp-atlas-api-client |
ApiClient, ClientCredentialsAuthProvider |
@mongodb-js/mcp-atlas-telemetry |
AtlasTelemetry (create({ logger, deviceId, apiClient, keychain, enabled, serverMetadata })), TelemetryConfig, TelemetryBaseEvent, TelemetryCommonProperties |
@mongodb-js/mcp-logging |
ConsoleLogger, DiskLogger, McpLogger |
@mongodb-js/mcp-metrics |
PrometheusMetrics, createDefaultMetrics |
@mongodb-js/mcp-ui |
UIRegistry |
@mongodb-js/mcp-types |
ServerMetadata, TransportRequestContext, ToolCategory, OperationType, UserConfig, IMetrics, DefaultMetricDefinitions, ISession, IToolSession, ITransportRunner |
Stdio:
import { StdioRunner } from "@mongodb-js/mcp-core";
import { CliServer } from "@mongodb-js/mcp-cli";
const runner = new StdioRunner({ logger, server: cliServer });
await runner.start();HTTP: StreamableHttpRunner attaches a MCPHttpServer to the transport. The runners start() the server and close() it; per-request server creation happens in MCPHttpServer.createServerForRequest. Optionally add a MonitoringServer for Prometheus metrics. See Use Case 2 for a full wiring example.
CLI default (per-request servers): the CLI's createHttpTransportRunnerFromConfig wires a CliMcpHttpServer that creates a fresh CliServer per HTTP session via createServerFromConfig, applying request-level config overrides (applyConfigOverrides) on each session — so concurrent HTTP sessions are isolated (separate servers, sessions, telemetry and scoped connection registries). App-level infrastructure (metrics, device id, shared connection store, Atlas Local client) is built once by createSharedServicesFromConfig and shared. Stdio builds a single server (one client per connection).
parseUserConfig reads CLI args and env vars, producing the effective UserConfig. When allowRequestOverrides is enabled, clients may override config per request via HTTP headers (x-mongo-config-*) or query parameters (x-mongo-config-*); applyConfigOverrides({ baseConfig, request }) applies those overrides. configRegistry describes every config field, its overridability, and its comparison behavior.
AtlasTelemetry.create({ logger, deviceId, apiClient, keychain, enabled, serverMetadata }) from @mongodb-js/mcp-atlas-telemetry. keychain and serverMetadata are required — serverMetadata is your ServerMetadata (mcpServerName, version, engines). To customize common properties, subclass AtlasTelemetry and override getCommonProperties(). In tests use NoopTelemetry from @mongodb-js/mcp-core.
import { McpLogger } from "@mongodb-js/mcp-logging";
new McpLogger({
server: mcpServer,
options: { logLevel: server.mcpLogLevel },
keychain,
});ConsoleLogger writes to the console; DiskLogger writes to disk. All loggers accept options objects (e.g. new LoggerBase({ keychain })).
MCPConnectionManager (from @mongodb-js/mcp-tools-mongodb) manages MongoDB connections with display-name sanitization, redaction, and connection state tracking. connectionErrorHandler, ErrorCodes, and MongoDBError cover user-facing connection errors. Use formatUntrustedData (from @mongodb-js/mcp-core) when echoing untrusted data back to clients.
UIRegistry (from @mongodb-js/mcp-ui) registers the MCP UI components (e.g. ListDatabases) exposed as MCP resources. The default Resources from @mongodb-js/mcp-cli already includes them.
import {
runMcpCli,
Resources,
DryRunHandler,
HelpHandler,
VersionHandler,
} from "@mongodb-js/mcp-cli";
import { MongoDBTools } from "@mongodb-js/mcp-tools-mongodb";
import { AtlasTools } from "@mongodb-js/mcp-tools-atlas";
import type { ServerMetadata } from "@mongodb-js/mcp-types";
const serverMetadata: ServerMetadata = {
mcpServerName: "my-product-mcp",
version: "1.0.0",
engines: { node: process.version },
};
const tools = [...MongoDBTools, ...AtlasTools];
await runMcpCli({
args: process.argv.slice(2),
serverMetadata,
consoleLogger: console,
onExit: (code) => process.exit(code),
tools,
resources: Resources,
handlers: [
new HelpHandler(),
new VersionHandler(),
new DryRunHandler({ tools, resources: Resources }),
],
});See Use Case 2 for the complete MCPHttpServer-based wiring, including SessionStore and StreamableHttpRunner.
See Use Case 3 for the ToolBase subclass pattern (static toolName/category/operationType, description, zod argsShape, execute, resolveTelemetryMetadata).
The pre-v3 mongodb-mcp-server single-package library API (Server, Session, StreamableHttpRunner.createServerForRequest, mongodb-mcp-server/tools and /web entry points, defaultCreate* helpers, positional constructor arguments, …) is removed in v3. See the repository's v1 → v3 migration guide for the complete symbol-by-symbol mapping, or run the migration skill's inventory script to scan your consumer code.