# Defense-in-Depth Score: MongoDB MCP Server

**Repo:** https://github.com/mongodb-js/mongodb-mcp-server · **Commit:** `b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d` · **Reviewed:** 2026-10-03
**What it is:** MCP server for MongoDB databases and Atlas clusters
**Category:** Data & Analytics
**Scored configuration:** Local stdio server started with no flags and a connection string in MDB_MCP_CONNECTION_STRING (code defaults: readOnly=false, server-side JS blocked, confirmations for drop/delete tools via elicitation, telemetry on); Atlas tools are off unless Atlas API credentials are configured.
**Agent surface (default):** code execution no · filesystem write yes · network egress yes · external credentials yes · persistent memory no · untrusted input yes · third party extensions no · sub agents no · external communication no

## Score: 4.8 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L1 | L1 | L1 | 0.17 | — | **0.17** | High |
| C2 | Approval gates | L1 | L1 | L2 | L0 | 0.25 | — | **0.25** | High |
| C3 | Tool & action scoping | L2 | L2 | L2 | L1 | 0.45 | — | **0.45** | High |
| C4 | Code-execution isolation | L1 | L2 | L2 | L2 | 0.42 | — | **0.42** | High |
| C5 | Untrusted input blast radius | L2 | L2 | L2 | L0 | 0.40 | C5-WORSTCASE | **0.25** | High |
| C6 | Memory, context & configuration integrity | SA | SA | SA | SA | 1.00 | — | **1.00** (SA) | High |
| C7 | Third-party extensions | SA | SA | SA | SA | 1.00 | — | **1.00** (SA) | High |
| C8 | Secrets & sensitive-data protection | L2 | L3 | L1 | L1 | 0.47 | — | **0.47** | High |
| C9 | Audit & traceability | L1 | L2 | L2 | L1 | 0.38 | — | **0.38** | High |
| C10 | Limits & kill switch | L2 | L1 | L2 | L1 | 0.38 | — | **0.38** | High |

Controls where a risk surface exists: 2.83 / 8.0 (35%); 2 criteria scored SA (surface absent).

A carefully engineered server with real safety features (read-only mode, blocked server-side JavaScript, untrusted-data wrapping, secret redaction, read caps, and confirmations for drop and delete tools), but most of them are partial or opt-in. As shipped, writes are enabled, update-many and insert-many need no confirmation, the confirmation gate does not cover every path, and the connect tool accepts any host. Prompt-injected text in the database can therefore both copy data to an attacker's server and irreversibly change data with no human involved. Run it with --readOnly and a least-privilege database user.

## Critical gaps
- In the default configuration a hijacked model can exfiltrate data (connect to an arbitrary MongoDB host, then insert-many) and irreversibly modify data (update-many, no confirmation) with no human in the loop. (ASI01, LLM01, T6; C5) — [packages/tools-mongodb/src/tools/connect/connect.ts:11-12](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L11-L12); [packages/tools-mongodb/src/tools/create/insertMany.ts:45](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/create/insertMany.ts#L45); [packages/tools-mongodb/src/tools/update/updateMany.ts:89-91](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/update/updateMany.ts#L89-L91); [packages/cli/src/config/userConfig.ts:110-118](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L110-L118)

## Criterion details

### C1 Identity & least privilege — 0.17 (high)

The server acts with whatever MongoDB credential the operator puts in the connection string and never narrows it: read-only mode only hides tools, it does not swap in a read-only credential. The connect tool also accepts any connection string the model supplies, so credentials found in data or chat can be used to open further deployments. The Atlas path is better designed (each Atlas cluster connection mints a temporary, cluster-scoped database user that expires after four hours), but it is off unless Atlas API credentials are configured, and in that mode the atlas-create-db-user tool can mint users with any built-in role, including atlasAdmin.

- **S L0:** One operator-supplied connection string backs every MongoDB tool with no narrowing (read and write share it; readOnly hides tools but keeps the credential), and the connect tool will use any other credential the model supplies. — [packages/cli/src/config/userConfig.ts:49-55](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L49-L55); [packages/core/src/toolBase.ts:843-849](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L843-L849); [packages/tools-mongodb/src/tools/connect/connect.ts:11-12](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L11-L12) (verified)
  - *To reach the next level:* The server neither refuses credentials it was not given nor narrows the one it was given; a dedicated, role-scoped identity would be L1-L2.
- **C L1:** The connect tool lets the model open connections with arbitrary credentials and hosts, so not every action uses the operator's chosen identity; the server performs no authorization check of its own. — [packages/tools-mongodb/src/tools/connect/connect.ts:11-12](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L11-L12); [packages/tools-mongodb/src/tools/connect/connect.ts:46-50](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L46-L50) (verified)
  - *To reach the next level:* No single authorization layer: model-supplied connection strings bypass the configured identity entirely.
- **D L1:** Write tools are enabled by default (readOnly defaults to false); the README and setup wizard add --readOnly to their examples, but that is documentation, not the code default. — [packages/cli/src/config/userConfig.ts:110-118](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L110-L118); [README.md:108](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/README.md#L108) (verified)
  - *To reach the next level:* Default is not read-only; least privilege requires the operator to pass --readOnly.
- **B L1:** A hijack inherits the connection string's full role on the configured deployment (often a production database) plus any other deployment whose credentials the model can supply to connect; with opt-in Atlas credentials it extends to project/org administration. — [packages/tools-mongodb/src/tools/connect/connect.ts:46-50](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L46-L50); [packages/tools-atlas/src/helpers/roles.ts:42-48](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-atlas/src/helpers/roles.ts#L42-L48) (verified)
  - *To reach the next level:* Nothing bounds the identity to one tenant or to non-destructive writes; credentials are long-lived.
- **Cap:** none
- **Notes:** Opt-in Atlas mode: atlas-connect-cluster creates a temporary user scoped to one cluster with deleteAfterDate = now + 4h (packages/tools-atlas/src/tools/connect/connectCluster.ts:99-125), with readAnyDatabase in readOnly mode. In the same mode atlas-create-db-user accepts any built-in role including atlasAdmin (createDBUser.ts:28), a self-escalation path (C1-SELFESC would apply if Atlas credentials were the scored default). Not credited because Atlas tools register only when apiClientId/apiClientSecret are set (atlasTool.ts:37-42).

### C2 Approval gates — 0.25 (high)

The server ships its own confirmation step: a configurable list of tools (drop-database, drop-collection, delete-many, drop-index, Atlas user and access-list creation, stream changes) asks the user through MCP elicitation, and aggregation pipelines that write ($out/$merge) are confirmed separately. But many state-changing tools are not on the list (update-many, insert-many, rename-collection, create-index, Atlas cluster creation, pause and upgrade, Atlas Local deployment deletion), the confirmation gate does not cover every path, and some risk annotations are inaccurate. A wrongly allowed call can permanently destroy data with no undo.

- **S L1:** Read/write annotations derive from each tool's operation type, but some are inaccurate. — [packages/core/src/toolBase.ts:468-479](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L468-L479) (verified)
  - *To reach the next level:* Accurate readOnlyHint/destructiveHint on every tool (L2), then dry-runs for destructive operations.
- **C L1:** Only tools named in confirmationRequiredTools are gated; update-many, insert-many, rename-collection, create-index and Atlas cluster create/pause/upgrade run with no confirmation. — [packages/cli/src/config/userConfig.ts:87-98](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L87-L98); [packages/core/src/toolBase.ts:689-691](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L689-L691); [packages/tools-mongodb/src/tools/update/updateMany.ts:89-91](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/update/updateMany.ts#L89-L91) (verified)
  - *To reach the next level:* All mutating tools need to be gated, not a hand-picked list.
- **D L2:** The gate is on by default for the listed tools, but it does not cover every path, and the list is ordinary config. (verified)
  - *To reach the next level:* Harden gate enforcement and warn loudly when confirmations are disabled.
- **B L0:** drop-database, delete-many and update-many permanently remove or overwrite data with no checkpoint or undo. — [packages/tools-mongodb/src/tools/delete/deleteMany.ts:78](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/delete/deleteMany.ts#L78); [packages/tools-mongodb/src/tools/update/updateMany.ts:89-91](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/update/updateMany.ts#L89-L91); [packages/tools-mongodb/src/tools/delete/deleteMany.ts:110](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/delete/deleteMany.ts#L110) (verified)
  - *To reach the next level:* No reversible path (backup snapshot or soft delete) before destructive operations.
- **Cap:** none

### C3 Tool & action scoping — 0.45 (high)

Tools are narrow and typed: separate find, count, insert, update, delete and drop tools, each with a strict schema that rejects unknown arguments, and Atlas arguments (project IDs, IPs, CIDRs, role names) are validated with patterns. Reads are capped (100 documents and 16 MB by default). However, the connect tool takes any connection string, so a hijacked model can reach arbitrary hosts, and the write tools take arbitrary filters with no bound on how many documents they affect. Everything, including write and delete tools, is enabled by default unless the operator passes --readOnly.

- **S L2:** Strict zod schemas on every tool and MQL guards, but connection strings (host and credentials) are unconstrained and delete/update filters are unbounded. — [packages/core/src/toolBase.ts:773-776](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L773-L776); [packages/tools-mongodb/src/tools/connect/connect.ts:11-12](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L11-L12); [packages/tools-mongodb/src/tools/delete/deleteMany.ts:78](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/delete/deleteMany.ts#L78) (verified)
  - *To reach the next level:* No host allowlist for connect, and no numeric bound on documents affected by update-many/delete-many.
- **C L2:** Schema validation is applied centrally at registration and every filter-taking MongoDB tool calls assertMqlIsAllowed, but the connect tool (the egress path) validates nothing beyond the argument being a string. — [packages/core/src/toolBase.ts:797](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L797); [packages/tools-mongodb/src/mongodbTool.ts:175-182](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/mongodbTool.ts#L175-L182) (verified)
  - *To reach the next level:* Validate the connect tool's target (host allowlist or confirmation for new hosts) so that every built-in tool constrains its inputs.
- **D L2:** Tool groups can be disabled by category, operation type or name, and readOnly drops all write tools, but the default set includes create/update/delete tools. — [packages/cli/src/config/userConfig.ts:110-118](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L110-L118); [packages/core/src/toolBase.ts:847-853](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L847-L853) (verified)
  - *To reach the next level:* Default to the read-only tool set and require explicit enabling of write tools.
- **B L1:** A misused tool reaches every database the credential can, plus any host given to connect; destructive operations have no quantity bound. — [packages/tools-mongodb/src/tools/connect/connect.ts:46-50](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L46-L50); [packages/tools-mongodb/src/tools/delete/deleteMany.ts:78](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/delete/deleteMany.ts#L78) (verified)
  - *To reach the next level:* Writes are not scoped to particular databases/collections or bounded in count.
- **Cap:** none

### C4 Code-execution isolation — 0.42 (high)

The server runs no shell or local code, but it does send model-written MongoDB queries and aggregation pipelines to the database, the equivalent of raw SQL. Server-side JavaScript operators ($where, $function, $accumulator) are rejected by default, through a recursive scan applied to every filter and pipeline the tools forward. Beyond that filter there is no isolation primitive: queries run with the connection's full database role, and the JavaScript block can be turned off with an ordinary config value.

- **S L1:** The only control is a recursive denylist of server-side JavaScript operators; other MQL runs with the connection's full privileges. — [packages/tools-mongodb/src/helpers/mqlGuards.ts:7](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/helpers/mqlGuards.ts#L7); [packages/tools-mongodb/src/helpers/mqlGuards.ts:18-37](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/helpers/mqlGuards.ts#L18-L37) (verified)
  - *To reach the next level:* No separate execution boundary (e.g., a restricted database role or read-only session enforced by the server).
- **C L2:** The guard is called on filters, projections and pipelines in find, count, aggregate, aggregate-db, export, explain, update-many and delete-many; update-many's update document is not scanned but is object-only, so it cannot carry an expression pipeline. — [packages/tools-mongodb/src/mongodbTool.ts:219-221](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/mongodbTool.ts#L219-L221); [packages/tools-mongodb/src/tools/update/updateMany.ts:58](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/update/updateMany.ts#L58); [packages/tools-mongodb/src/args.ts:12-13](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/args.ts#L12-L13) (verified)
  - *To reach the next level:* Coverage is limited by the filter's own strength; no single choke point that every forwarded MQL fragment must pass.
- **D L2:** disableServerSideJs defaults to true and per-request overrides can only tighten it, but the operator can turn it off with an env var or flag without warning. — [packages/cli/src/config/userConfig.ts:135-143](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L135-L143) (verified)
  - *To reach the next level:* Disabling should require an explicit, loudly named flag.
- **B L2:** If the filter is bypassed, code runs inside mongod's JavaScript engine with the connection's database role; there is no host filesystem or shell access from the server itself. — [packages/tools-mongodb/src/mongodbTool.ts:109-111](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/mongodbTool.ts#L109-L111) (verified)
  - *To reach the next level:* Reach is the whole deployment under the connection's role, not a scoped or read-only context.
- **Cap:** none

### C5 Untrusted input blast radius — 0.25 (high)

Documents, schemas, logs and Atlas data returned to the model are wrapped by default in randomly named 'untrusted-user-data' tags with a warning, and structured results are kept separate from the summary text. That is labelling, not a barrier. In the default configuration, a model hijacked by text stored in the database can, with no human involved, copy data out by connecting to an attacker's MongoDB server and inserting it there, and can irreversibly overwrite data with update-many, which needs no confirmation. Read-only mode removes the write leg but is off by default.

- **S L2:** Outputs separate structuredContent from text, and untrusted data is wrapped in per-call UUID tags with a warning, but there is no machine-readable provenance or untrusted flag in metadata. — [packages/core/src/toolBase.ts:1118-1131](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L1118-L1131); [packages/tools-mongodb/src/tools/read/find.ts:148-162](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/read/find.ts#L148-L162) (verified)
  - *To reach the next level:* Provenance (source namespace, an untrusted flag) carried as metadata the host can act on.
- **C L2:** Most read paths (find, aggregate, schema, logs, list tools, Atlas reads, knowledge search) use formatUntrustedData; error messages carrying database-derived strings are returned unwrapped. — [packages/core/src/toolBase.ts:904-913](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L904-L913); [packages/tools-mongodb/src/tools/metadata/logs.ts:71](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/metadata/logs.ts#L71) (verified)
  - *To reach the next level:* Every source, including error text and tool-result metadata, should be marked.
- **D L2:** Wrapping is on by default; the operator can disable it silently, and with allowRequestOverrides it can be turned off per request. — [packages/cli/src/config/userConfig.ts:128-134](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L128-L134); [packages/core/src/toolBase.ts:1106-1112](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L1106-L1112) (verified)
  - *To reach the next level:* Disabling should be explicit and warned.
- **B L0:** Unattended exfiltration (connect to any host, then insert-many) and unattended irreversible change (update-many, rename-collection) are both possible in the default config. — [packages/tools-mongodb/src/tools/connect/connect.ts:46-50](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/connect/connect.ts#L46-L50); [packages/tools-mongodb/src/tools/create/insertMany.ts:45](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/create/insertMany.ts#L45); [packages/tools-mongodb/src/tools/update/updateMany.ts:89-91](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/update/updateMany.ts#L89-L91) (verified)
  - *To reach the next level:* Break the combination: deny unconfirmed egress to new hosts or default to read-only.
- **Cap:** C5-WORSTCASE — Blast radius is at the lowest level by default: a hijacked model can both exfiltrate data and irreversibly modify it without a human.

### C6 Memory, context & configuration integrity — 1.00 (high)

The server keeps no memory the model can write to and loads no instruction or settings files from a working directory. Configuration comes only from environment variables, command-line flags or an explicitly named config file, and connections and exports live in memory or expire within minutes.

- **Structural absence:** searched `rg -n --glob '!*.test.ts' 'dotenv|AGENTS\.md|CLAUDE\.md|\.mcp\.json|vectorstore|embeddings?Store|saveMemory|remember\('` in `packages/cli/src packages/core/src packages/tools-mongodb/src packages/tools-atlas/src packages/tools-atlas-local/src packages/tools-assistant/src packages/mongodb-mcp-server/src packages/http-runners/src packages/logging/src` → 0 hits (No .env loading, instruction files, project MCP configs or memory stores in any runtime package.); [packages/cli/src/config/parseUserConfig.ts:17-21](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/parseUserConfig.ts#L17-L21)

### C7 Third-party extensions — 1.00 (high)

The server has no plugin system and launches no third-party MCP servers or packages at runtime. Its only dynamic imports are its own bundled dependencies. The optional Atlas Local tools pull MongoDB's own container image, the product being managed, not a third-party extension.

- **Structural absence:** searched `rg -n --glob '!*.test.ts' 'await import\(|plugin|npx|npm install|pip install|trust_remote_code|pickle'` in `packages/cli/src packages/core/src packages/tools-mongodb/src packages/tools-atlas/src packages/tools-atlas-local/src packages/tools-assistant/src packages/mongodb-mcp-server/src packages/http-runners/src packages/logging/src` → 10 hits (2 hits are dynamic imports of bundled dependencies (@mongodb-js/atlas-local, node-machine-id); 8 are comments/event names for the MongoDB driver's built-in OIDC auth plugin, not an extension loader.)

### C8 Secrets & sensitive-data protection — 0.47 (high)

Secrets from configuration (connection string, Atlas client secret, AWS and TLS values, Voyage key) go into an immutable redaction keychain, and every logger (disk, stderr and the log stream sent to the MCP client) redacts by default, as do tool error messages and the config resource. Temporary Atlas user credentials are scrubbed from errors too. Gaps: secrets are stored as plain env or config values, anonymous usage telemetry is on by default, atlas-create-db-user returns generated passwords to the model by design, and the connect tool has the model handle full connection strings.

- **S L2:** Keychain-based redaction on every logger and on error paths; credentials themselves live in plaintext env/config, not an OS keychain. — [packages/core/src/logging/redactingLoggerBase.ts:25-36](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/logging/redactingLoggerBase.ts#L25-L36); [packages/cli/src/config/createKeychainFromConfig.ts:45](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/createKeychainFromConfig.ts#L45) (verified)
  - *To reach the next level:* No secret-manager/OS-keychain storage, and secrets can still enter model context via connect and atlas-create-db-user.
- **C L3:** Redaction covers disk/stderr/MCP logs, tool error messages, the config resource and Atlas temp-credential errors; telemetry carries no content; no subprocesses are spawned. — [packages/core/src/toolBase.ts:904-905](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L904-L905); [packages/cli/src/resources/common/config.ts:57](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/resources/common/config.ts#L57); [packages/tools-atlas/src/tools/connect/connectCluster.ts:191-194](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-atlas/src/tools/connect/connectCluster.ts#L191-L194) (verified)
  - *To reach the next level:* Generated DB-user passwords are returned to the model and model-supplied connection strings pass through context.
- **D L1:** Anonymous, content-free telemetry to MongoDB is on by default (opt out via config or DO_NOT_TRACK); redaction is on by default. — [packages/cli/src/config/userConfig.ts:144-146](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L144-L146); [packages/atlas-telemetry/src/atlasTelemetry.ts:254-265](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/atlas-telemetry/src/atlasTelemetry.ts#L254-L265) (verified)
  - *To reach the next level:* Telemetry should be opt-in.
- **B L1:** Leaked material would be long-lived database credentials (and, in Atlas mode, a long-lived service-account secret) with whatever role the operator granted. — [packages/cli/src/config/userConfig.ts:39-55](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L39-L55) (verified)
  - *To reach the next level:* Scoped, short-lived credentials for the default path (as the Atlas temp-user flow already does).
- **Cap:** none

### C9 Audit & traceability — 0.38 (high)

Every tool call goes through one invoke path that writes JSON log lines (tool name and request ID) to a disk log in the user's home directory with 30-day retention, and confirmation requests and outcomes are logged. The record does not include the arguments of the call, so after an incident you can see that delete-many ran but not which filter it used. Logging is best effort and does not block actions.

- **S L1:** Tool execution is logged by name and request ID only (at debug level, which the disk logger records); arguments and approver are not recorded. — [packages/core/src/toolBase.ts:625-631](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L625-L631); [packages/core/src/toolBase.ts:719-729](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L719-L729) (verified)
  - *To reach the next level:* Record each call's arguments, result status and timestamp in a structured audit entry.
- **C L2:** All built-in tools pass through ToolBase.invoke, so every call (and every confirmation outcome) gets a log line; there are no extensions or sub-agents. — [packages/core/src/toolBase.ts:567-570](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/core/src/toolBase.ts#L567-L570) (verified)
  - *To reach the next level:* Configuration changes and credential use (e.g., temp Atlas users) are not systematically recorded.
- **D L2:** Disk logging is on by default and stored outside any workspace (~/.mongodb/mongodb-mcp/.app-logs), but the operator can disable it and the server process can alter it. — [packages/cli/src/config/userConfig.ts:56-76](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L56-L76); [packages/cli/src/createLoggerFromConfig.ts:22-32](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/createLoggerFromConfig.ts#L22-L32) (verified)
  - *To reach the next level:* Write the record via a component outside the server process, or tamper-evidently.
- **B L1:** The log writer is asynchronous and best-effort; errors go to console and actions proceed. — [packages/cli/src/createLoggerFromConfig.ts:25-30](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/createLoggerFromConfig.ts#L25-L30) (verified)
  - *To reach the next level:* Flush a record per action and surface logging failures.
- **Cap:** none

### C10 Limits & kill switch — 0.38 (high)

Reads are bounded by the server: find and aggregate return at most 100 documents and 16 MB by default (with a 1 MB default per-response limit the model cannot push beyond the configured maximum), and the count phase has time caps. The export tool, update-many and delete-many have no bound, no server-side time limit is set by default, and there is no rate limiting. Cancellation signals are passed to the driver, but a long-running server-side write may continue after the client stops waiting.

- **S L2:** Server-enforced document and byte caps on find/aggregate and time caps on count phases; no caps on export or writes. — [packages/tools-mongodb/src/tools/read/find.ts:134-139](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/read/find.ts#L134-L139); [packages/cli/src/config/userConfig.ts:267-273](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L267-L273) (verified)
  - *To reach the next level:* Caps on every operation (export, update-many, delete-many) and concurrency/rate limits.
- **C L1:** Limits cover the read tools only; maxTimeMS is applied only when the operator sets it. — [packages/tools-mongodb/src/mongodbTool.ts:137-141](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/mongodbTool.ts#L137-L141); [packages/tools-mongodb/src/tools/read/export.ts:106-112](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/read/export.ts#L106-L112) (verified)
  - *To reach the next level:* Apply a default server-side time limit to all operations, including writes and export.
- **D L2:** Sensible read defaults the model cannot exceed, configurable by the operator; maxTimeMS is unset by default. — [packages/cli/src/config/userConfig.ts:274-282](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/cli/src/config/userConfig.ts#L274-L282) (verified)
  - *To reach the next level:* A default operation time limit and hard ceilings that configuration cannot exceed.
- **B L1:** An unbounded update-many/delete-many or export can run indefinitely server-side; aborting the request does not guarantee the server operation stops. — [packages/tools-mongodb/src/tools/delete/deleteMany.ts:78](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-mongodb/src/tools/delete/deleteMany.ts#L78); [packages/tools-atlas/src/tools/connect/connectCluster.ts:205](https://github.com/mongodb-js/mongodb-mcp-server/blob/b5c4bb42e743a2d4c4fc950d1e236ce9c31c6f6d/packages/tools-atlas/src/tools/connect/connectCluster.ts#L205) (verified)
  - *To reach the next level:* Tight default time ceilings and server-side kill of in-flight operations on cancel.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: Database documents, schemas and mongod logs returned to the model (packages/tools-mongodb/src/tools/read/find.ts:148) · [B] sensitive data/systems: Operator's MongoDB credential and all data it can read (packages/cli/src/config/userConfig.ts:49) · [C] state change / egress: update-many/insert-many writes and connect to arbitrary hosts (packages/tools-mongodb/src/tools/connect/connect.ts:46) · Same default session? Yes

## Highest-impact improvements
1. Make readOnly the code default (write tools opt-in), matching the README examples. — C3 D L2→L3, +0.050 before caps (Playbook 3)
2. Harden enforcement of the confirmation gate. — C2 D L2→L3, +0.050 before caps (Playbook 5)
3. Add update-many, insert-many, rename-collection, create-index and Atlas cluster create/pause/upgrade to the default confirmation list. — C2 C L1→L2, +0.075 before caps (Playbook 5)
4. Log each tool call's arguments (redacted) alongside name, result and request ID. — C9 S L1→L2, +0.075 before caps (Playbook 1 step 3)
5. Correct risk annotations on mutating tools. — C2 S L1→L2, +0.075 before caps (Playbook 5)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Scored the code defaults (readOnly=false). The README examples and setup wizard add --readOnly; deployments that follow them get a substantially smaller blast radius (C1-D, C3-D and C5-B would improve).
- Atlas tools (cluster/user/access-list management) and Atlas Local (Docker) tools were reviewed but are only enabled when Atlas credentials or a running Docker daemon are present; their risks are noted in C1/C2 notes rather than scored as the default.
- Scope limited to runtime packages (cli, core, tools-*, mongodb-mcp-server, http-runners, logging, atlas-api-client, atlas-telemetry); the separate mongodb-atlas-mcp-remote proxy, setup wizard, UI bundle and test packages were not scored. HTTP transport (loopback-only by default, no built-in auth) was not the scored mode.
- Behaviour of the MCP SDK's elicitation shim and of the MongoDB driver (cancellation semantics, OIDC plugin) is inferred from the code that calls them, not verified in those libraries.
- No reviewer-directed instructions or AGENTS.md/CLAUDE.md files were found in the repository.
