Developer's Guide to Embedding and Extending the MongoDB MCP Server
September 16, 2026 ยท View on GitHub
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.
๐ Table of Contents
Overview
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 - Request-scoped server creation hooks โ
MCPHttpServer.createServerForRequest(a freshCliServerper HTTP request; app-level services are shared) - Tool registration โ
ToolBase/ToolClasstool classes andToolRegistryarrays - Connection management and connection error handling โ
MCPConnectionManager,connectionErrorHandler
v3 is sessionless. There is no
Session/CliSessionobject and no per-client session state anywhere. Each HTTP request (or stdio connection) is served by a fresh request-scopedCliServer; every heavy dependency (connections, exports, API client, telemetry, metrics, keychain) is built once per process and shared insideSharedServerServices. Tools and resources read services offthis.serverand derive per-request data (including client identity) from the tool request โ they never hold a session.
Installation
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-runners
All packages are available as ES modules. The server targets Node.js >= 24.
Core Concepts
The entry points
| Package | Role |
|---|---|
@mongodb-js/mcp-cli | Primary entry point. Custom CLI (runMcpCli), the request-scoped server class (CliServer), config (parseUserConfig, UserConfigSchema, configRegistry, applyConfigOverrides), createSharedServicesFromConfig + createServerFromConfig + createRunnerFromConfig + createHttpTransportRunnerFromConfig, Resources, CLI handlers |
@mongodb-js/mcp-core | Transports (StdioRunner, InMemoryTransport), tool base classes (ToolBase, ToolClass) + toToolExecutionContext, Keychain/IRedactor, Elicitation, NoopLogger, NoopTelemetry, deprecated SessionStore (2025-era legacy) |
@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) |
Customizing Server Behavior
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 every request-scoped server (metrics,monitoringServer,keychain,deviceId,connectionStore,connectionRegistry,apiClient,exportsManager,telemetry,atlasLocalClient,config,tools,resources).createServerFromConfig({ config, sharedServices, request, connectionScope })builds one request-scopedCliServerfrom a resolved config.request(an optionalTransportRequestContext) is present for HTTP โ the server then gets an isolated connection registry view keyed by theconnectionScopepolicy and carriestransportRequestthrough to tool/resource constructors.connectionScopeis required wheneverrequestis supplied:createServerFromConfigthrows if an HTTP request arrives without a policy (fail closed, no implicit default). It returnsCliServerdirectly.createRunnerFromConfigcallscreateSharedServicesFromConfiginternally and returns only the configured transport runner (CliStdioRunner/StdioRunnerfor stdio,StreamableHttpRunnerfor HTTP).createHttpTransportRunnerFromConfig(sharedServices)wires HTTP with the CLI'sCliMcpHttpServer.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 customization, subclassMCPHttpServerand overridecreateServerForRequest(request: TransportRequestContext): Promise<TServer>(return a request-scopedCliServerviacreateServerFromConfig). In v3 this hook lives onMCPHttpServer, not onStreamableHttpRunner. - Inject HTTP middleware by overriding
MCPHttpServer.registerMiddlewares()โ called after body parsing and header validation, ahead of the/mcproute handlers. Addthis.app.use(...)for auth, rate-limiting, or observability; it runs per/mcprequest. For a custom legacy session store, overridecreateLegacyHandler()to pass anISessionStoreto theLegacyMcpHttpHandler.
Server metadata
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.
Architecture
The MongoDB MCP Server library follows a modular architecture:
- Transport runners:
CliStdioRunner/StdioRunner(stdio) andStreamableHttpRunner(HTTP) manage the MCP transport layer. Runners attach a pre-built server โ they no longer build one for you. CliServer: the request-scoped server that wraps theMcpServerand registers tools, resources and capabilities. A fresh instance is built per HTTP request (or stdio connection); it holds the effective config and a client-scoped connection registry view. There is no session object โCliServeris the whole per-request composition.SharedServerServices(fromcreateSharedServicesFromConfig): the app-level services built once per process and shared by every request-scoped server (metrics, keychain, device id, connection store/registry, API client, exports manager, telemetry, Atlas Local client, monitoring server). App-level services deliberately carry no per-client state.ToolServer/ToolServices: the service surface a tool reads from its construction-timethis.server(config,logger,keychain,telemetry,elicitation,metrics,uiRegistry,mcpServer,tools,isToolCategoryAvailable). Category services (e.g.MongoDBToolServicesaddsconnectionRegistry/connectionErrorHandler/exportsManager) travel in theTServicesgeneric.- 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). Each request-scoped server carries its effective config onserver.config.
Use Cases
Use Case 1: Override Server Configuration
Configure the MCP server with custom settings, such as HTTP headers for authentication before serving an MCP client, or replace parts of the default infrastructure.
Example: Setting HTTP Headers for Authentication
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 { createKeychainFromConfig } from "@mongodb-js/mcp-cli";
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 keychain = createKeychainFromConfig({ config });
const logger = await createLoggerFromConfig({ config, keychain });
const transportRunner = await createRunnerFromConfig({
config: {
...config,
httpHeaders: {
"x-api-key": "your-secret-api-key",
},
},
serverMetadata,
tools: [...MongoDBTools],
resources: Resources,
logger,
keychain,
});
await startRunner({
transportRunner,
logger,
onExit: (code) => process.exit(code),
});
Clients connecting to this server must include the specified headers in their requests, otherwise their initialization request is declined.
Example: Replacing Infrastructure Pieces
Use individual create*FromConfig factories to swap dependencies:
import {
createLoggerFromConfig,
createApiClientFromConfig,
} from "@mongodb-js/mcp-cli";
import { createKeychainFromConfig } from "@mongodb-js/mcp-cli";
const keychain = createKeychainFromConfig({ config });
const logger = await createLoggerFromConfig({ config, keychain });
const apiClient = createApiClientFromConfig({ config, serverMetadata, logger });
Available factories: createLoggerFromConfig, createApiClientFromConfig, createExportsManagerFromConfig, createTelemetryFromConfig, createMonitoringServerFromConfig.
Use Case 2: Request-Scoped Configuration
Customize each request-scoped server โ enabling user-specific permissions and settings based on request headers, query parameters, or authentication context โ by subclassing MCPHttpServer and overriding createServerForRequest(request: TransportRequestContext) to build a CliServer per request from app-level SharedServerServices.
The v1 pattern of overriding
createServerForRequestonStreamableHttpRunneris removed in v3. Runners no longer create servers, and there is noSessionobject to customize โ the per-requestCliServer(built bycreateServerFromConfig) is the customization point.
Example: User-Based Tool Permissions (HTTP)
import {
MCPHttpServer,
StreamableHttpRunner,
} from "@mongodb-js/mcp-http-runners";
import {
parseUserConfig,
createLoggerFromConfig,
createSharedServicesFromConfig,
createServerFromConfig,
Resources,
type CliServer,
type SharedServerServices,
} from "@mongodb-js/mcp-cli";
import { createKeychainFromConfig } from "@mongodb-js/mcp-cli";
import { MongoDBTools } from "@mongodb-js/mcp-tools-mongodb";
import type {
TransportRequestContext,
ServerMetadata,
} 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 },
};
// App-level services, built once per process and shared by every server
const { parsed: baseConfig } = parseUserConfig({ args: process.argv.slice(2) });
const keychain = createKeychainFromConfig({ config: baseConfig });
const logger = await createLoggerFromConfig({ config: baseConfig, keychain });
const sharedServices: SharedServerServices =
await createSharedServicesFromConfig({
config: baseConfig,
serverMetadata,
tools: MongoDBTools,
resources: Resources,
logger,
keychain,
});
// A request-scoped server per HTTP request: every heavy service comes from
// sharedServices; only the config, the connection registry view and the
// request-scoped McpServer/Elicitation/CliServer are created fresh per request.
class PermissionsMCPHttpServer extends MCPHttpServer<CliServer> {
private readonly sharedServices: SharedServerServices;
constructor(sharedServices: SharedServerServices) {
super({
options: {
http: {
host: baseConfig.httpHost,
port: baseConfig.httpPort,
bodyLimit: baseConfig.httpBodyLimit,
headers: baseConfig.httpHeaders,
responseType: baseConfig.httpResponseType,
// The server never authenticates on its own: hosts that require
// verified identity enforce it in middleware (registerMiddlewares),
// which runs before protocol dispatch for both modern and legacy
// traffic, and inject it as `req.auth`.
},
},
logger,
metrics: sharedServices.metrics,
});
this.sharedServices = sharedServices;
}
protected override async createServerForRequest(
request: TransportRequestContext
): Promise<CliServer> {
// Use the host-verified identity, not a client-controlled header: the host's
// token verifier attaches the OIDC `sub` claim to `AuthInfo.extra` (via
// `req.auth`). Use the claim verbatim โ reject a missing or non-string
// subject; do not trim it.
const sub = request.authInfo?.extra?.sub;
if (typeof sub !== "string" || sub === "") {
throw new Error("User authentication required: no verified sub claim");
}
const permissions = await getUserPermissions(sub);
const allOperations = [
"read",
"metadata",
"create",
"update",
"delete",
"connect",
];
const disabledTools = allOperations.filter(
(op) => !permissions.allowedOperations.includes(op)
);
// The effective (possibly request-overridden) config for this request.
// The server gets an isolated connection registry view keyed by the
// connectionScope policy. This example scopes per verified user: the host's
// token verifier must attach the OIDC `sub` claim via `AuthInfo.extra`;
// a request without `sub` returns `undefined`, which is an ephemeral,
// per-request scope (fail closed โ it never silently shares).
return createServerFromConfig({
config: {
...baseConfig,
disabledTools,
readOnly: permissions.role === "analyst",
maxDocumentsPerQuery: permissions.maxDocuments,
},
sharedServices: this.sharedServices,
request,
// Per-user scope, keyed on the verified principal. Fail closed: a
// non-string or blank `sub` is not a usable principal, so return undefined
// (ephemeral). JSON-encode the tuple so it is injective regardless of the
// claim values (a `sub` containing a delimiter or quote cannot collide).
connectionScope: (req) => {
const sub = req.authInfo?.extra?.sub;
if (typeof sub !== "string" || sub === "") {
return undefined;
}
return `user:${JSON.stringify([req.authInfo.clientId, sub])}`;
},
});
}
}
const mcpHttpServer = new PermissionsMCPHttpServer(sharedServices);
const runner = new StreamableHttpRunner({
logger,
mcpHttpServer,
monitoringServer: sharedServices.monitoringServer,
});
await runner.start();
Note:
MongoDBTools,MCPConnectionStore,DeviceId, andConnectionRegistrycome from@mongodb-js/mcp-tools-mongodb(see Connection management); a real embedding typically wirescreateSharedServicesFromConfigonce (as above) and builds only the request-scopedCliServerper request viacreateServerFromConfig. MongoDB connection state deliberately lives at the app level (connectionStore/connectionRegistry), not in any session โ tools address connections byconnectionId, and per-requestcreateServerFromConfigscopes that registry per request (stable scope for identified clients, ephemeral otherwise).
Use Case 3: Adding Custom Tools
Implement custom tools by extending ToolBase from @mongodb-js/mcp-core:
import {
ToolBase,
type ToolClass,
type ToolCategory,
type OperationType,
} from "@mongodb-js/mcp-core";
import type { ToolExecutionContext } from "@mongodb-js/mcp-types";
import { z } from "zod";
class MyCustomTool extends ToolBase {
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, { request }: ToolExecutionContext) {
// Tool implementation โ arguments are inferred from argsShape. The effective
// config is read off `this.server.config`; per-request data (request.id,
// request.headers, request.clientInfo, ...) arrives on `request`.
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. Constructors receive { server, transportRequest } where server is the request-scoped ToolServer carrying the individually-injected services (server.logger, server.telemetry, server.elicitation, server.config, server.keychain, server.metrics, ...) plus the shared infrastructure (server.mcpServer, server.tools, server.isToolCategoryAvailable); there is no session object. The TServices generic narrows the app-level services a tool category reads (e.g. MongoDBToolServer adds connectionRegistry/connectionErrorHandler/exportsManager). Per-request data โ the raw request, signal, request id, headers, client identity โ travels on the request argument of execute(args, { request }) (ToolExecutionContext.request); the effective config lives on this.server.config. Use formatUntrustedData (from @mongodb-js/mcp-core) to format arbitrary data in tool output, and Elicitation (from @mongodb-js/mcp-core) for multi-round-trip confirmation/input.
Use Case 4: Selective Tool Registration
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.
API Reference
@mongodb-js/mcp-cli
| Symbol | Description |
|---|---|
runMcpCli({ args, serverMetadata, consoleLogger, onExit, tools, resources, handlers? }) | Run the full CLI: parse config โ handlers โ create infrastructure โ start server |
CliServer / CliServerOptions | The request-scoped server wrapping the McpServer; a fresh instance per HTTP request / stdio connection |
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) |
createSharedServicesFromConfig({ config, serverMetadata, tools, resources, logger }) | Build app-level infra shared by every request-scoped server (metrics, keychain, connection store/registry, API client, exports, telemetry, ...) |
createServerFromConfig({ config, sharedServices, request? }) | Build one request-scoped CliServer from a resolved config and shared services |
createRunnerFromConfig({ config, serverMetadata, tools, resources, logger }) | Build shared services + the transport runner only (CliStdioRunner / StreamableHttpRunner) |
createHttpTransportRunnerFromConfig(sharedServices) | Build the HTTP transport runner with a CliMcpHttpServer (fresh CliServer per request) |
CliMcpHttpServer / CliStdioRunner | HTTP / stdio servers that build a fresh request-scoped CliServer per request |
closeSharedServices(sharedServices) / SharedServerServices | Release app-level services on shutdown / the shared app-level services container |
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, RunMcpCliOptions, SharedServerServices |
@mongodb-js/mcp-core
| Symbol | Description |
|---|---|
ToolBase, ToolClass, ToolArgs, ToolResult, formatUntrustedData | Custom tool authoring |
toToolExecutionContext | Adapts the SDK ServerContext to a ToolExecutionContext (builds the per-request object; the request-scoped server is carried on the tool, not the request) |
StdioRunner({ logger }) | Abstract stdio transport runner (serveStdio; override createServer() to return a registered McpServer) |
InMemoryTransport | In-memory transport for tests |
SessionStore, createDefaultSessionStore | Deprecated legacy 2025-era HTTP session store |
Keychain | Secret storage/redaction (the IRedactor type lives in @mongodb-js/mcp-types); constructor takes the config secrets and Keychain.redact(value)/redactErrorMessage(error) replace the removed allSecrets field. Built once from config via createKeychainFromConfig |
Elicitation | Multi-round-trip confirmation/input (confirmationRequired/readConfirmation/inputRequired/readInput) |
NoopLogger, NoopTelemetry, LoggerBase, RedactingLoggerBase, CompositeLogger | Logging/telemetry primitives |
McpServer (re-export) | @modelcontextprotocol/server |
@mongodb-js/mcp-http-runners
| Symbol | Description |
|---|---|
StreamableHttpRunner / StreamableHttpRunnerOptions | HTTP transport runner |
MCPHttpServer / MCPHttpServerOptions | HTTP server; override abstract createServerForRequest(request: TransportRequestContext): Promise<TServer>, and optionally registerMiddlewares() for host middleware (including auth enforcement โ the server never authenticates on its own). sessionOptions is for the legacy 2025-era lifecycle only |
MonitoringServer / MonitoringServerOptions | Optional /metrics monitoring server |
ExpressBasedHttpServer | Base class for Express-based HTTP servers |
Other packages
| Package | Symbols |
|---|---|
@mongodb-js/mcp-tools-mongodb | MongoDBTools, MongoDBToolBase, MongoDBToolServer, MongoDBToolServices, MCPConnectionManager, ConnectionManager, MCPConnectionStore, 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, ConnectionScopePolicy, RequestAuthInfo, ToolCategory, OperationType, UserConfig, IMetrics, DefaultMetricDefinitions, ITransportRunner, BaseServer, ToolServer, ToolServices, ToolRequest, ToolExecutionContext, ResourceServices, ResourceServerArg, IRedactor |
Advanced Topics
Transports
Stdio: subclass StdioRunner (or use CliStdioRunner) and override createServer() to return a registered CliServer/McpServer (one per stdio connection). The constructor takes only the logger โ server creation is a method override, mirroring the HTTP pattern:
import { StdioRunner } from "@mongodb-js/mcp-core";
import { createServerFromConfig } from "@mongodb-js/mcp-cli";
class MyStdioRunner extends StdioRunner {
protected override async createServer() {
const server = createServerFromConfig({ config, sharedServices });
await server.register(); // register tools/resources/capabilities without a transport
return server.mcpServer;
}
}
const runner = new MyStdioRunner({ logger });
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 (request-scoped servers): the CLI's createHttpTransportRunnerFromConfig wires a CliMcpHttpServer that creates a fresh request-scoped CliServer per HTTP request via createServerFromConfig, applying request-level config overrides (applyConfigOverrides) on each request โ so concurrent HTTP requests are isolated (separate servers, request-scoped connection registry views, telemetry). App-level infrastructure (metrics, device id, shared connection store, API client, exports, telemetry, Atlas Local client) is built once by createSharedServicesFromConfig and shared. Stdio builds a single server (one client per connection).
Connection scoping (required for HTTP): CliMcpHttpServer requires an explicit connectionScope policy โ this is the knob that controls connection isolation. connectionScope is a (request: TransportRequestContext) => string | undefined function; undefined means an ephemeral, per-request scope (no shared state, reaped when the request-scoped server closes). It must be keyed on whatever distinguishes the callers. The library ships connectionScopeFromConfig, which maps the connectionScope config option to a policy: "session" (default) keys on the client's mcp-session-id, falling back to the shared scope on the sessionless 2026-07-28 path when no id is present; "global" shares one scope across all clients. For authenticated deployments the embedder supplies the policy (per-user sub / per-client clientId), and there is no self-asserted header-based policy.
clientId identifies the OAuth client application, not the end user โ so keying solely on clientId lets every user of one shared client registration share connections. It should be keyed on the verified end-user principal (the OIDC sub claim, which the token verifier attaches to AuthInfo.extra), fail-closing (returning undefined, i.e. ephemeral) when there is no usable subject, and JSON-encoding the tuple so claim values cannot collide:
const sub = req.authInfo?.extra?.sub;
if (typeof sub !== "string" || sub === "") return undefined;
return `user:${JSON.stringify([req.authInfo.clientId, sub])}`;
Keying by clientId is only appropriate for M2M/client-credentials tokens (where clientId is the principal) or single-user-per-client deployments.
Configuration and request overrides
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.
Telemetry
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.
Logging
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 })).
Connection management
MCPConnectionManager / MCPConnectionStore (from @mongodb-js/mcp-tools-mongodb) manage MongoDB connections with display-name sanitization, redaction, and connection state tracking. In the sessionless model the connection registry lives once at the app level (connectionStore); createServerFromConfig gives each request-scoped server a view of it (connectionStore.view({ scope, owned })) so connections are scoped per request (stable per identified client, ephemeral for anonymous requests) and addressed by opaque connectionId. connectionErrorHandler, ErrorCodes, and MongoDBError cover user-facing connection errors. Use formatUntrustedData (from @mongodb-js/mcp-core) when echoing untrusted data back to clients.
UI resources
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.
Examples
Example 1: Custom CLI with a custom tool
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 }),
],
});
Example 2: Full custom HTTP host with request-scoped config
See Use Case 2 for the complete MCPHttpServer-based wiring, including createSharedServicesFromConfig, a custom MCPHttpServer and StreamableHttpRunner.
Example 3: Custom tool class
See Use Case 3 for the ToolBase subclass pattern (static toolName/category/operationType, description, zod argsShape, execute, resolveTelemetryMetadata).
Migrating from the v1 single-package API
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.