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-server package 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 fresh CliServer per HTTP request; app-level services are shared)
  • Tool registration โ€” ToolBase / ToolClass tool classes and ToolRegistry arrays
  • Connection management and connection error handling โ€” MCPConnectionManager, connectionErrorHandler

v3 is sessionless. There is no Session / CliSession object and no per-client session state anywhere. Each HTTP request (or stdio connection) is served by a fresh request-scoped CliServer; every heavy dependency (connections, exports, API client, telemetry, metrics, keychain) is built once per process and shared inside SharedServerServices. Tools and resources read services off this.server and 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

PackageRole
@mongodb-js/mcp-cliPrimary 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-coreTransports (StdioRunner, InMemoryTransport), tool base classes (ToolBase, ToolClass) + toToolExecutionContext, Keychain/IRedactor, Elicitation, NoopLogger, NoopTelemetry, deprecated SessionStore (2025-era legacy)
@mongodb-js/mcp-http-runnersHTTP transport (StreamableHttpRunner, MCPHttpServer, MonitoringServer)
@mongodb-js/mcp-typesShared 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-clientAtlas Admin API client (ApiClient, ClientCredentialsAuthProvider)
@mongodb-js/mcp-atlas-telemetryTelemetry pipeline (AtlasTelemetry)
@mongodb-js/mcp-loggingLoggers (ConsoleLogger, DiskLogger, McpLogger)
@mongodb-js/mcp-metricsMetrics (PrometheusMetrics, createDefaultMetrics)
@mongodb-js/mcp-uiMCP UI registry (UIRegistry)

Customizing Server Behavior

There are three main approaches:

  1. 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.
  2. createSharedServicesFromConfig + createRunnerFromConfig + startRunner: split the same flow so you can replace individual dependencies (logger, API client, telemetry, monitoring server) via create*FromConfig factories, 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-scoped CliServer from a resolved config. request (an optional TransportRequestContext) is present for HTTP โ€” the server then gets an isolated connection registry view keyed by the connectionScope policy and carries transportRequest through to tool/resource constructors. connectionScope is required whenever request is supplied: createServerFromConfig throws if an HTTP request arrives without a policy (fail closed, no implicit default). It returns CliServer directly.
    • createRunnerFromConfig calls createSharedServicesFromConfig internally and returns only the configured transport runner (CliStdioRunner/StdioRunner for stdio, StreamableHttpRunner for HTTP). createHttpTransportRunnerFromConfig(sharedServices) wires HTTP with the CLI's CliMcpHttpServer.
    • startRunner({ transportRunner, logger, onExit }) starts the runner and manages the server lifecycle (signal handlers, graceful shutdown).
  3. Override MCPHttpServer.createServerForRequest: when hosting over HTTP and you need per-request customization, subclass MCPHttpServer and override createServerForRequest(request: TransportRequestContext): Promise<TServer> (return a request-scoped CliServer via createServerFromConfig). In v3 this hook lives on MCPHttpServer, not on StreamableHttpRunner.
  4. Inject HTTP middleware by overriding MCPHttpServer.registerMiddlewares() โ€” called after body parsing and header validation, ahead of the /mcp route handlers. Add this.app.use(...) for auth, rate-limiting, or observability; it runs per /mcp request. For a custom legacy session store, override createLegacyHandler() to pass an ISessionStore to the LegacyMcpHttpHandler.

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) and StreamableHttpRunner (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 the McpServer and 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 โ€” CliServer is the whole per-request composition.
  • SharedServerServices (from createSharedServicesFromConfig): 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-time this.server (config, logger, keychain, telemetry, elicitation, metrics, uiRegistry, mcpServer, tools, isToolCategoryAvailable). Category services (e.g. MongoDBToolServices adds connectionRegistry/connectionErrorHandler/exportsManager) travel in the TServices generic.
  • Tools: individual capabilities exposed to the MCP client, implemented as ToolBase subclasses and grouped into bundle arrays (MongoDBTools, AtlasTools, โ€ฆ).
  • Configuration: UserConfig parsed via parseUserConfig/UserConfigSchema, with request-level override mechanisms (applyConfigOverrides, configRegistry). Each request-scoped server carries its effective config on server.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 createServerForRequest on StreamableHttpRunner is removed in v3. Runners no longer create servers, and there is no Session object to customize โ€” the per-request CliServer (built by createServerFromConfig) 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, and ConnectionRegistry come from @mongodb-js/mcp-tools-mongodb (see Connection management); a real embedding typically wires createSharedServicesFromConfig once (as above) and builds only the request-scoped CliServer per request via createServerFromConfig. MongoDB connection state deliberately lives at the app level (connectionStore/connectionRegistry), not in any session โ€” tools address connections by connectionId, and per-request createServerFromConfig scopes 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

SymbolDescription
runMcpCli({ args, serverMetadata, consoleLogger, onExit, tools, resources, handlers? })Run the full CLI: parse config โ†’ handlers โ†’ create infrastructure โ†’ start server
CliServer / CliServerOptionsThe 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_KEYSConfig schema and registry
applyConfigOverrides, getConfigMeta, nameToConfigKeyRequest-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 / CliStdioRunnerHTTP / stdio servers that build a fresh request-scoped CliServer per request
closeSharedServices(sharedServices) / SharedServerServicesRelease 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 / createMonitoringServerFromConfigIndividual infrastructure factories
Resources, ConfigResource, DebugResource, ExportedDataBuilt-in MCP resources
HelpHandler, VersionHandler, DryRunHandlerCLI handlers

| Types | ToolRegistry, ResourceRegistry, RunMcpCliOptions, SharedServerServices |

@mongodb-js/mcp-core

SymbolDescription
ToolBase, ToolClass, ToolArgs, ToolResult, formatUntrustedDataCustom tool authoring
toToolExecutionContextAdapts 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)
InMemoryTransportIn-memory transport for tests
SessionStore, createDefaultSessionStoreDeprecated legacy 2025-era HTTP session store
KeychainSecret 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
ElicitationMulti-round-trip confirmation/input (confirmationRequired/readConfirmation/inputRequired/readInput)
NoopLogger, NoopTelemetry, LoggerBase, RedactingLoggerBase, CompositeLoggerLogging/telemetry primitives
McpServer (re-export)@modelcontextprotocol/server

@mongodb-js/mcp-http-runners

SymbolDescription
StreamableHttpRunner / StreamableHttpRunnerOptionsHTTP transport runner
MCPHttpServer / MCPHttpServerOptionsHTTP 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 / MonitoringServerOptionsOptional /metrics monitoring server
ExpressBasedHttpServerBase class for Express-based HTTP servers

Other packages

PackageSymbols
@mongodb-js/mcp-tools-mongodbMongoDBTools, MongoDBToolBase, MongoDBToolServer, MongoDBToolServices, MCPConnectionManager, ConnectionManager, MCPConnectionStore, ErrorCodes, MongoDBError, exports manager & connection types
@mongodb-js/mcp-tools-atlasAtlasTools, AtlasToolBase
@mongodb-js/mcp-tools-atlas-localAtlasLocalTools, createAtlasLocalClient
@mongodb-js/mcp-tools-assistantAssistantTools
@mongodb-js/mcp-atlas-api-clientApiClient, ClientCredentialsAuthProvider
@mongodb-js/mcp-atlas-telemetryAtlasTelemetry (create({ logger, deviceId, apiClient, keychain, enabled, serverMetadata })), TelemetryConfig, TelemetryBaseEvent, TelemetryCommonProperties
@mongodb-js/mcp-loggingConsoleLogger, DiskLogger, McpLogger
@mongodb-js/mcp-metricsPrometheusMetrics, createDefaultMetrics
@mongodb-js/mcp-uiUIRegistry
@mongodb-js/mcp-typesServerMetadata, 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.