MongoDB MCP Server v1 → v3 migration
This skill migrates consumer code: projects that embed, customize, or extend
mongodb-mcp-server as a library (custom CLIs, HTTP hosts, custom tools, selective tool
registration, request-scoped configuration). It runs in the consumer's repository, not the
mongodb-mcp-server repo.
MCP_SERVER_LIBRARY.md now documents the v3 API surface (the scoped @mongodb-js/mcp-*
packages, runMcpCli, CliServer, MCPHttpServer.createServerForRequest, ToolBase/
ToolClass, …). Use it as the reference for the migration target; the before/after
sections in this skill and its inventory script map v1/v2 consumer code onto that surface.
v3 is sessionless. The v3 server has no
Session/CliSessionobject and no per-client session state anywhere. Each HTTP request (or stdio connection) gets a fresh request-scopedCliServerbuilt bycreateServerFromConfig; every heavy dependency (connections, exports, API client, telemetry, metrics, keychain) is built once per process insideSharedServerServicesand shared. Tools/resources read services offthis.server(there is nosession), and per-client identity travels on the tool request (ToolExecutionContext.request.clientInfo) rather than on a session object.
The core rule
mongodb-mcp-server is not a library in v3.
- End users:
npx mongodb-mcp-serveror the MCPB binary only. - Do not
npm install mongodb-mcp-serverandimport { … } from "mongodb-mcp-server"in application code. - Do not use the legacy
mongodb-mcp-server/toolsormongodb-mcp-server/webentry points.
Embed via the scoped packages instead: @mongodb-js/mcp-cli (custom CLI), @mongodb-js/mcp-* for everything else.
Step 1 — Inventory consumer code
# repo root of the consumer project; resolves skill-relative scripts against this skill's dir
scripts/inventory-consumer-code.sh .
The script lists every file that imports mongodb-mcp-server, shows the matched import
lines, and classifies each imported symbol to its v3 package. Anything reported as
unrecognized — manual review is a symbol the table doesn't know: look it up in the
v3 migration guide and the package's API report before deciding.
Then get the full picture of every usage site:
rg -n 'mongodb-mcp-server|from "mongodb-mcp-server"|require\("mongodb-mcp-server"' --glob '!node_modules' --glob '!dist' .
Step 2 — Classify the use case
Pick the row(s) that match what the consumer does; install those packages (v3):
| Use case | npm install |
Primary v3 imports |
|---|---|---|
| Custom CLI (most v1 embeds) | @mongodb-js/mcp-cli + needed tool packages |
runMcpCli, createRunnerFromConfig, create*FromConfig, Resources, CliServer |
| Host MCP over stdio | @mongodb-js/mcp-core |
StdioRunner, SessionStore, Keychain, Elicitation, NoopTelemetry, InMemoryTransport |
| Host MCP over HTTP | @mongodb-js/mcp-http-runners @mongodb-js/mcp-core |
StreamableHttpRunner, MCPHttpServer, MonitoringServer |
| Embed server (advanced) | cli + core + http-runners + metrics + logging + telemetry + tools | CliServer, createSharedServicesFromConfig, createServerFromConfig, createRunnerFromConfig, startRunner |
| Config parsing / overrides | @mongodb-js/mcp-cli |
UserConfig, UserConfigSchema, parseUserConfig, applyConfigOverrides, configRegistry |
| Custom tools (any category) | @mongodb-js/mcp-core @mongodb-js/mcp-types |
ToolBase, ToolClass, OperationType, ToolCategory |
| MongoDB tools + connections | @mongodb-js/mcp-tools-mongodb |
FindTool, MongoDBToolBase, MCPConnectionManager, ErrorCodes, MongoDBError |
| Atlas Admin API tools | @mongodb-js/mcp-tools-atlas @mongodb-js/mcp-atlas-api-client |
AtlasTools, ApiClient, ClientCredentialsAuthProvider |
| Atlas Local tools | @mongodb-js/mcp-tools-atlas-local |
AtlasLocalTools, createAtlasLocalClient |
| Assistant / knowledge tools | @mongodb-js/mcp-tools-assistant |
AssistantTools |
| Telemetry | @mongodb-js/mcp-atlas-telemetry |
AtlasTelemetry, EventCache, TelemetryConfig |
| Logging | @mongodb-js/mcp-logging |
ConsoleLogger, DiskLogger, McpLogger |
| Metrics | @mongodb-js/mcp-metrics |
PrometheusMetrics, createDefaultMetrics |
| MCP UI resources | @mongodb-js/mcp-ui |
UIRegistry |
| Shared types | @mongodb-js/mcp-types |
TransportRequestContext, ITransportRunner, ToolServer, ToolServices, ServerMetadata |
Step 3 — Install and migrate the big three use cases
3a. Custom CLI → runMcpCli
Most v1 embeds become one runMcpCli call (same flow as the official v3 binary):
parse config → handlers → create server → start stdio/HTTP.
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";
/** Ideally read/generated from package.json */
const serverMetadata: ServerMetadata = {
mcpServerName: "my-product-mcp",
version: "1.0.0",
engines: { node: ">=24" },
};
const tools = [...MongoDBTools, ...AtlasTools /*, MyTool */];
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 }),
],
});
Escalation ladder if they need more control: createServerFromConfig / createRunnerFromConfig + startRunner
(both @mongodb-js/mcp-cli) → CliServer + @mongodb-js/mcp-http-runners for per-request
HTTP.
3b. HTTP host → MCPHttpServer + StreamableHttpRunner
Per-request server creation moved off the runner. In v1,
createServerForRequest lived on StreamableHttpRunner; in v3 it lives on
MCPHttpServer. Runners no longer accept userConfig or build the server — build the
server first, attach transport. The simplest path is the CLI's own
CliMcpHttpServer + createHttpTransportRunnerFromConfig, which wires an
MCPHttpServer that builds a fresh request-scoped CliServer per request from
SharedServerServices:
import {
createSharedServicesFromConfig,
createHttpTransportRunnerFromConfig,
} from "@mongodb-js/mcp-cli";
const sharedServices = await createSharedServicesFromConfig({
config,
serverMetadata,
tools,
resources,
logger,
});
const runner = createHttpTransportRunnerFromConfig(sharedServices);
await runner.start();
To also apply per-request config overrides or control connection isolation, subclass
MCPHttpServer and override createServerForRequest to return a request-scoped
CliServer built with createServerFromConfig.
- class CustomRunner extends StreamableHttpRunner {
- protected override async createServerForRequest({ request }) {
- return this.createServer({ userConfig: sessionConfig });
- }
- }
+ const connectionScope = (request: TransportRequestContext): string | undefined => {
+ // Fail closed: a missing / non-string / empty sub is not a usable principal.
+ const sub = request.authInfo?.extra?.sub;
+ if (typeof sub !== "string" || sub === "") {
+ return undefined;
+ }
+ // JSON-encode the tuple so it is injective regardless of the claim values
+ // (a sub containing a delimiter or quote cannot collide).
+ return `user:${JSON.stringify([request.authInfo.clientId, sub])}`;
+ };
+ class MyMCPHttpServer extends MCPHttpServer<CliServer> {
+ protected override async createServerForRequest(
+ request: TransportRequestContext
+ ): Promise<CliServer> {
+ const config = applyConfigOverrides({ baseConfig: this.sharedServices.config, request });
+ return createServerFromConfig({ config, sharedServices: this.sharedServices, request, connectionScope });
+ }
+ }
+ const mcpHttpServer = new MyMCPHttpServer({
+ options: {
+ http: {
+ host: config.httpHost,
+ port: config.httpPort,
+ bodyLimit: config.httpBodyLimit,
+ headers: config.httpHeaders,
+ responseType: config.httpResponseType,
+ },
+ },
+ logger,
+ metrics,
+ });
+ const runner = new StreamableHttpRunner({ logger, metrics, mcpHttpServer });
Note the v3 MCPHttpServer takes options.http and an optional sessionOptions for the
legacy 2025-era lifecycle — there is no session: block and no SessionStore to
build. App-level services (keychain, connectionStore, exportsManager, apiClient,
telemetry, …) are built once by createSharedServicesFromConfig and passed in as
SharedServerServices; the request-scoped server holds no per-client session state
(hosts that require verified identity enforce it in their own middleware and inject it
as req.auth, which both the modern and legacy paths forward as the request's
authInfo; connections are scoped per request via the connectionScope policy, which
keys on the request's verified identity). See
Use Case 2.
Still may extends StreamableHttpRunner to customize start()/close() or bundle the
wiring in a constructor — just don't override createServerForRequest there.
Stdio, for completeness: new StdioRunner({ userConfig: config }) → subclass
StdioRunner (or use CliStdioRunner) and override createServer() (import
@mongodb-js/mcp-core; the constructor takes only { logger }). The runner serves
through the SDK's serveStdio entry (protocol revision 2026-07-28 and 2025-era):
createServer() returns a registered CliServer (await server.register() before
returning server.mcpServer), built fresh per stdio connection.
3c. Config
- import { parseUserConfig, applyConfigOverrides, type UserConfig } from "mongodb-mcp-server";
+ import { parseUserConfig, applyConfigOverrides, type UserConfig } from "@mongodb-js/mcp-cli";
- applyConfigOverrides({ baseConfig, request?: RequestContext });
+ applyConfigOverrides({ baseConfig, request?: TransportRequestContext }); // type from @mongodb-js/mcp-types
parseArgsWithCliOptions → parseUserConfig. Config moved from the server onto the
request-scoped server: server.userConfig → server.config (there is no session
object — tools/resources read config off their construction-time this.server).
applyConfigOverrides applies request-level overrides to a base UserConfig; on HTTP
each request produces its own config via applyConfigOverrides({ baseConfig, request }).
Step 4 — Migrate remaining symbols
Renamed symbols
| v1 (old) | v3 (new) | Package |
|---|---|---|
Server / ServerOptions |
CliServer / CliServerOptions (request-scoped) |
@mongodb-js/mcp-cli |
Session / SessionOptions |
removed — the per-client session is gone. Config now lives on the request-scoped server (server.config); per-client identity travels on the tool request (ToolExecutionContext.request.clientInfo). No CliSession exists in v3. |
— |
Telemetry |
AtlasTelemetry |
@mongodb-js/mcp-atlas-telemetry |
BaseEvent |
TelemetryBaseEvent |
@mongodb-js/mcp-atlas-telemetry |
CommonProperties |
TelemetryCommonProperties |
@mongodb-js/mcp-atlas-telemetry |
NullLogger |
NoopLogger |
@mongodb-js/mcp-core |
RequestContext |
TransportRequestContext |
@mongodb-js/mcp-types |
TransportRunnerBase |
ITransportRunner |
@mongodb-js/mcp-types |
Metrics<T> / DefaultMetrics |
IMetrics<T> / DefaultMetricDefinitions |
@mongodb-js/mcp-types |
MCPHttpServerConstructorArgs |
MCPHttpServerOptions |
@mongodb-js/mcp-http-runners |
MonitoringServerConstructorArgs |
MonitoringServerOptions |
@mongodb-js/mcp-http-runners |
StreamableHttpTransportRunnerConfig |
StreamableHttpRunnerOptions + wired MCPHttpServer |
@mongodb-js/mcp-http-runners |
defaultCreateApiClient |
createApiClientFromConfig or construct ApiClient |
@mongodb-js/mcp-cli / @mongodb-js/mcp-atlas-api-client |
defaultCreateAtlasLocalClient |
createAtlasLocalClient |
@mongodb-js/mcp-tools-atlas-local |
defaultCreateConnectionManager / createMCPConnectionManager |
createConnectionManagerFromConfig or new MCPConnectionManager({...}) |
@mongodb-js/mcp-cli / @mongodb-js/mcp-tools-mongodb |
createDefaultMcpHttpServer / createDefaultMonitoringServer / createDefaultSessionStore |
new MCPHttpServer(...) / new MonitoringServer(...) / new SessionStore(...) |
@mongodb-js/mcp-http-runners / @mongodb-js/mcp-core |
createServicesFromUserConfig |
createServerFromConfig + createRunnerFromConfig |
@mongodb-js/mcp-cli |
parseArgsWithCliOptions |
parseUserConfig |
@mongodb-js/mcp-cli |
tool classes (e.g. FindTool) |
same names, new package | @mongodb-js/mcp-tools-* |
Removed from the v1 public API — do not import
ApiClientFactoryFn, BaseEvent, CommonProperties, CreateMcpHttpServerFn,
CreateMonitoringServerFn, CreateSessionConfigFn, CreateSessionStoreFn, Credentials,
CustomizableServerOptions, CustomizableSessionOptions, MCPHttpServerConstructorArgs,
MonitoringServerConfig, MonitoringServerConstructorArgs, NullLogger,
RequestContext, Server, ServerOptions, Session, SessionOptions, CliSession,
CliSessionOptions, StreamableHttpTransportRunnerConfig, Telemetry,
TransportRunnerBase, TransportRunnerConfig, UIRegistryOptions,
createDefaultMcpHttpServer, createDefaultMonitoringServer, createDefaultSessionStore,
createMCPConnectionManager, defaultCreateApiClient, defaultCreateAtlasLocalClient,
defaultCreateConnectionManager, parseArgsWithCliOptions
Handle each with the rename table above or the replacements below.
Constructor shape changes (same concepts, different args)
- new LoggerBase(keychain); // also ConsoleLogger, DiskLogger
+ new LoggerBase({ keychain });
- new CompositeLogger(a, b);
+ new CompositeLogger({ loggers: [a, b], keychain });
- new ApiClient(options, logger, authProvider);
+ new ApiClient({ options: { baseUrl, userAgent }, logger, authProvider });
- new MCPConnectionManager(userConfig, logger, deviceId);
+ new MCPConnectionManager({ logger, deviceId, options: { connectionInfo: config, displayName, version } });
- new ConnectionStateConnected(sp, info, atlas);
+ new ConnectionStateConnected({ serviceProvider: sp, connectionStringInfo: info, connectedAtlasCluster: atlas });
Telemetry
- import { Telemetry, type BaseEvent, type CommonProperties } from "mongodb-mcp-server";
+ import { AtlasTelemetry, type TelemetryBaseEvent, type TelemetryCommonProperties } from "@mongodb-js/mcp-atlas-telemetry";
- Telemetry.create(session, userConfig, deviceId, { getCommonProperties: () => ({...}) });
+ AtlasTelemetry.create({
+ logger,
+ deviceId,
+ apiClient,
+ keychain,
+ enabled: config.telemetry === "enabled",
+ serverMetadata: packageInfo,
+ });
getCommonProperties callback → subclass AtlasTelemetry and override
getCommonProperties(), calling super. keychain and serverMetadata are now
required. Tests use NoopTelemetry from @mongodb-js/mcp-core.
Tools and custom tools
- import { FindTool, MongoDBToolBase } from "mongodb-mcp-server/tools";
+ import { FindTool, MongoDBToolBase } from "@mongodb-js/mcp-tools-mongodb";
- import { AllTools } from "mongodb-mcp-server/tools";
+ import { MongoDBTools } from "@mongodb-js/mcp-tools-mongodb";
+ import { AtlasTools } from "@mongodb-js/mcp-tools-atlas";
+ const tools = [...MongoDBTools, ...AtlasTools];
Bundles: MongoDBTools (@mongodb-js/mcp-tools-mongodb), AtlasTools
(@mongodb-js/mcp-tools-atlas), AtlasLocalTools (@mongodb-js/mcp-tools-atlas-local),
AssistantTools (@mongodb-js/mcp-tools-assistant).
Custom tool classes: ToolBase/ToolClass from @mongodb-js/mcp-core. There is no
TSession — the constructor receives { server, transportRequest } and tools read
config/services off this.server (ToolServer). The TServices generic narrows the
app-level services a tool category reads: MongoDBToolServer (from
@mongodb-js/mcp-tools-mongodb) extends ToolServer with connectionRegistry,
connectionErrorHandler and exportsManager, and MongoDBToolServices narrows the
config to IMongoDBConfig. Use UserConfigSchema.parse for defaults. execute now
receives (args, { request }); per-request data (request.headers, request.id,
request.clientInfo, request.inputResponses, …) travels on the request, while the
effective config lives on this.server.config. ToolCategory gains "custom".
Sessionless HTTP serving (MCPHttpServer)
v3 hosts MCP over HTTP through MCPHttpServer (@mongodb-js/mcp-http-runners), which
serves both the 2026-07-28 stateless protocol (each request builds a fresh
request-scoped server) and the 2025-era legacy sessionful protocol (via an internal
LegacyMcpHttpHandler). Key deltas from a sessionful embed:
MCPHttpServerhas nosessionStoreoption. Its options are{ options: { http }, logger, metrics, sessionOptions? };sessionOptions(maxSessions,idleTimeoutMS,notificationTimeoutMS,evictionIdleGraceMS) configure the default legacySessionStore. To inject your own, overridecreateLegacyHandler(see below).createServerForRequestreturns a server-scopedCliServer(noSession). The base callsserver.register()for you before handing theMcpServerto the transport, so a subclass must not register resources/tools itself. Build the server withtransportRequestso tools see the per-request headers/auth.- Inject HTTP middleware by overriding
protected registerMiddlewares(): void(called after body parsing + header validation, before the/mcproutes);this.app.use(...)your auth/rate-limiter/observability. - Inject a custom session store for the legacy path: override
MCPHttpServer.createLegacyHandlerto pass an auth-aware or durableISessionStoreto theLegacyMcpHttpHandler(which requires one).
Customizing via create*FromConfig factories
When overriding only part of the stack, use individual factories from @mongodb-js/mcp-cli:
const keychain = createKeychainFromConfig({ config }); // @mongodb-js/mcp-cli
const logger = await createLoggerFromConfig({ config, keychain });
const apiClient = createApiClientFromConfig({ config, serverMetadata, logger });
| v1 helper | v3 replacement |
|---|---|
defaultCreateApiClient |
createApiClientFromConfig or new ApiClient(...) |
createDefaultMonitoringServer |
createMonitoringServerFromConfig or new MonitoringServer(...) |
| ad-hoc logger from config | createLoggerFromConfig |
Full stack alternative: build app-level services once with
createSharedServicesFromConfig, then createServerFromConfig returns a
request-scoped CliServer directly (the logger is provided as input; the
heavy services come from sharedServices). For HTTP, pass both request and a
connectionScope policy — it is required whenever request is present and
createServerFromConfig throws without it (fail closed). For stdio, omit request
(or pass only the config). createRunnerFromConfig calls
createSharedServicesFromConfig internally and returns only the configured transport
runner; closeSharedServices(sharedServices) releases app-level services on shutdown.
Symbols that keep their names
UserConfig, UserConfigSchema, parseUserConfig, applyConfigOverrides, ApiClient,
ConnectionManager, MCPConnectionManager, connectionErrorHandler, ErrorCodes,
MongoDBError, EventCache, ExportsManager, DeviceId, UIRegistry,
JSON_RPC_ERROR_CODE_*, packageInfo (new in v3).
Names that survive but changed shape: Keychain (now IRedactor-compatible —
redact(value) replaces the removed allSecrets field), Elicitation (now
multi-round-trip confirmationRequired/readConfirmation/inputRequired/readInput
instead of requestConfirmation/requestInput), and SessionStore (deprecated —
only the 2025-era legacy transport still uses it).
Step 5 — Verify
- Remove the old dependency:
npm uninstall mongodb-mcp-server(keep it only if the app shells out to the binary). - Typecheck the whole project:
npx tsc --noEmit(or the project's build command). - Runtime smoke test: run the custom CLI / host and exercise one tool + telemetry.
- If the consumer had v1 pattern-guides (
MCP_SERVER_LIBRARY.mdexamples), diff usage against the v3 migration guide in the mongodb-mcp-server repo.
Working with subagents (larger migrations)
- Inventory triage: after Step 1, hand one file (or one module cluster) per subagent:
"Migrate this file's mongodb-mcp-server imports to v3 scoped packages using the
mapping in the mongodb-mcp-v3-migration skill. Produce a diff." Worktree note: if the
consumer's repo is a plain working tree, subagents must only analyze/draft — the main
agent applies edits sequentially. For parallel editing, give each subagent its own
worktree (the agent tool's
worktree_path) and merge/cherry-pick their commits. - Symbol lookup: an
Exploreagent can map any symbol not in the tables by reading the relevant@mongodb-js/mcp-*package's API report in the installed node_modules. - Final check: a subagent re-runs
tsc --noEmitand greps for any lingeringmongodb-mcp-serverimports.
Pitfalls
serverMetadatais required onCliServerOptionsandAtlasTelemetry.create— v1 code never passed it; usepackageInfofrom@mongodb-js/mcp-coreor build it from the consumer's package.json.mongodb-mcp-server/toolsand/webdon't exist in v3 — any deep import breaks; use the scoped packages.- No session object exists — v3 is sessionless: there is nothing named
Session,CliSession,ISessionorIToolSession. Config lives on the request-scoped server (this.server.config); per-client identity travels on the tool request (ToolExecutionContext.request.clientInfo). Grep forsession.after migrating. - Runner constructor changed — passing
userConfigto a runner is a v1-only API and will not typecheck. - Tool generics changed — old three-type-param
ToolBasecode must drop toToolBase<ToolServer>(constructor{ server, transportRequest }) and source config fromthis.server.config, not a session. MongoDB tools should targetMongoDBToolServer/MongoDBToolServices. - Type-only imports — the v3 packages enforce
import type { … }for types (erasableSyntaxOnly); fix any value/type mixed imports flagged by the compiler.