Skip to content

Migrate to MCP SDK v2 (dual-era) + security hardening + convertTime — v3.0.0 - #44

Merged
kiliczsh merged 14 commits into
mainfrom
v2-migration
Jul 29, 2026
Merged

Migrate to MCP SDK v2 (dual-era) + security hardening + convertTime — v3.0.0#44
kiliczsh merged 14 commits into
mainfrom
v2-migration

Conversation

@kiliczsh

@kiliczsh kiliczsh commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates mcp-mongo-server to the MCP SDK v2 while keeping full backward compatibility, and layers on a round of security hardening and two new capabilities.

Pins @modelcontextprotocol/*@^2.0.0 (stable) and releases as 3.0.0 (major — see Breaking changes).

What changed

Protocol / SDK

  • Migrate to MCP SDK v2 (scoped packages: @modelcontextprotocol/server, /node, /express). Uses serveStdio for stdio and createMcpHandler + toNodeHandler for HTTP.
  • Dual-era protocol support — negotiates the 2026-07-28 protocol while still serving legacy 2025-11-25 clients, so existing integrations keep working.
  • Error semantics aligned with the newer spec: validation/lookup misses surface as InvalidParams (-32602) via ProtocolError instead of the legacy -32002.
  • Dropped the forced secondary readPreference in read-only mode (was silently steering reads to secondaries).

Security hardening (all opt-in, safe by default)

  • Cross-database scoping — aggregation pipelines are pinned to the connected database. Cross-db targets ($lookup/$out/$merge into another db) are rejected unless --allow-cross-db (MCP_MONGODB_ALLOW_CROSS_DB).
  • Server-side JavaScript blocked by default$where, $function, $accumulator, mapReduce require --allow-server-js (MCP_MONGODB_ALLOW_SERVER_JS).
  • System-collection guard hardened (case-insensitive system.* match) for both tools and resources.
  • HTTP body limit made configurable (--json-limit / MCP_HTTP_JSON_LIMIT); oversized/malformed bodies return a proper JSON-RPC error instead of a raw stack.
  • Nesting-depth guard on the aggregation operator scan to prevent stack overflow on deeply nested input.
  • Optional bearer-token auth for the HTTP transport (--auth-token / MCP_HTTP_AUTH_TOKEN), constant-time comparison, no separate auth server needed.

Features & fixes

  • convertTime tool — converts a Unix timestamp or date string into UTC ISO 8601 / GMT / Unix seconds & ms, and reports the server's current time and timezone. Helps build unambiguous date queries across timezones. Date filters now honor explicit timezone offsets.
  • sort parameter fix — the query tool now declares sort in its input schema; previously the SDK stripped it, so sorts were silently ignored.
  • createIndex result fix — the driver returns the created index names (string[]), not the object the handler assumed, so success responses came back as an empty {}. Now returns { acknowledged, createdIndexes, indexCount }.

Docs

  • README + docs/ updated: HTTP transport, all new flags/env vars, security section, convertTime, bearer auth, corrected read-only behavior.

Breaking changes (why 3.0.0)

  • Runs on the MCP SDK v2 line and negotiates the 2026-07-28 protocol (legacy 2025-11-25 clients still supported).
  • Default-safe posture is stricter: cross-database targets and server-side JavaScript are now rejected unless explicitly enabled via the new flags.
  • Minimum MongoDB server version is 4.2+ (modern driver requirement) — older servers (wire version < 8) can no longer be connected.

Validation

  • Working tree clean; build and biome lint pass (0 warnings).
  • All 9 tools verified end-to-end against a live MongoDB 8.2 replica set (serverInfo, listCollections, count, query incl. sort, aggregate, insert, update, createIndex, convertTime).
  • Verified across the compatibility matrix (standalone 4.4 / 6.0 / 7.0, replica set 8.2, TLS, no-auth) over both stdio and HTTP transports.
  • bun audit: 3 moderate advisories (ajv ReDoS via $data) — transitive through the SDK, not exercised by this code.

kiliczsh added 11 commits July 22, 2026 20:22
Move from the monolithic @modelcontextprotocol/sdk to the v2 scoped
packages (server/node/express). The same server now serves both the
2026-07-28 and legacy 2025-era protocols from one factory.

- stdio via serveStdio(), HTTP via createMcpHandler() + toNodeHandler()
- request context is ctx.mcpReq (signal, notify-based progress)
- errors use ProtocolError / ProtocolErrorCode
- handler returns typed against SDK result types (no 'as any')
- resources/read validates the URI and returns InvalidParams (-32602)
- experimental tasks removed (no v2 equivalent; moved to Extensions Track)
- drop the zod dependency (now transitive via the SDK) and require Node 20
- add HTTP transport usage (--transport http, --port, --allowed-origins)
  and the Origin/403 DNS-rebinding note to the README and integration guide
- fix the read-only description: it blocks write tools and aggregation
  write/JS operators, and no longer claims a secondary read preference
- add a Security section recommending a read-only DB user for defense in depth
- note the Node 20 requirement
Three limits surfaced while probing the server:

- HTTP request bodies were capped at Express's 100kb default, far below
  stdio's ~10mb. Default to 10mb and make it configurable via --json-limit
  or MCP_HTTP_JSON_LIMIT.
- Body-parser failures (payload too large, malformed JSON) returned an HTML
  error page; they now return a proper JSON-RPC error.
- Deeply nested input could overflow the stack in processObjectIdInFilter;
  it now rejects nesting beyond 100 levels (MongoDB's own BSON limit) with a
  clear error.
An aggregation $out/$merge/$lookup targeting a different database could
read from or write to any database the connection's credentials allow,
escaping the database the server was pointed at. These stages are now
rejected when their target database differs from the connected one.

Opt back in to cross-database pipelines with --allow-cross-db or
MCP_MONGODB_ALLOW_CROSS_DB=true. Same-database operations are unaffected.
- add --allow-cross-db / MCP_MONGODB_ALLOW_CROSS_DB and
  --json-limit / MCP_HTTP_JSON_LIMIT to the options and env tables
- expand the Security section: database scope, read-only coverage of
  write/JS aggregation stages, and least-privilege DB users
- note the 10mb HTTP body default and how to change it in the integration guide
…ction guard

Aggregation $function/$where/$accumulator run arbitrary JavaScript on the
MongoDB server (a code-execution and DoS surface). They are now rejected by
default even outside read-only mode; opt back in with --allow-server-js or
MCP_MONGODB_ALLOW_SERVER_JS=true.

Also make the system-collection guard case-insensitive so casings like
'System.' can't slip past it. Documents the new flag in the README.
findAggOperator recurses over the whole pipeline for the read-only and
server-side-JS checks, which run before processObjectIdInFilter's depth
guard. A pathologically deep pipeline could overflow the stack there
first, surfacing an opaque 'Maximum call stack size exceeded'. It now
enforces the same MAX_OBJECT_DEPTH limit and reports a clear error.
Add --auth-token / MCP_HTTP_AUTH_TOKEN to require an
'Authorization: Bearer <token>' header on the HTTP endpoint. Missing or
invalid tokens get a 401 with a WWW-Authenticate Bearer challenge; the
token is compared in constant time. When unset, the endpoint stays open
as before. This is a simple shared-secret check aligned with the MCP
authorization spec's bearer mechanism, not a full OAuth flow (stdio keeps
using environment-based credentials). Documented in the README and
integration guide.
Add a convertTime tool that reports the current server time (with timezone)
or converts a Unix timestamp / date string into UTC ISO 8601, GMT, and Unix
seconds/milliseconds. This lets clients build unambiguous date queries when
the server and user are in different timezones.

Also broaden the date-string detection used when parsing filters to accept an
explicit numeric offset (e.g. +03:00) in addition to 'Z', so offset-qualified
timestamps convert to the correct instant. Timezone-less strings are still
left untouched to avoid server-local interpretation.
The query handler already applied a sort option, but the query tool never
declared 'sort' in its inputSchema, so the SDK dropped the argument before
it reached the handler — sort was silently ignored and results came back in
natural order. Declaring it makes sorting work through the tool interface.
@kiliczsh kiliczsh changed the title Migrate to MCP SDK v2 (dual-era) + security hardening + convertTime Migrate to MCP SDK v2 (dual-era) + Security Hardening Jul 22, 2026
@kiliczsh kiliczsh changed the title Migrate to MCP SDK v2 (dual-era) + Security Hardening Migrate to MCP SDK v2 + Security Hardening Jul 22, 2026
@kiliczsh kiliczsh self-assigned this Jul 22, 2026
@kiliczsh

Copy link
Copy Markdown
Owner Author

@modelcontextprotocol/server@2.0.0 is out. mcp-mongo-server can be verified and published with major version bump.

@kiliczsh kiliczsh changed the title Migrate to MCP SDK v2 + Security Hardening Migrate to MCP SDK v2 (dual-era) + security hardening + convertTime — v3.0.0 Jul 29, 2026
@kiliczsh
kiliczsh marked this pull request as ready for review July 29, 2026 11:28
@kiliczsh
kiliczsh merged commit 9ea8487 into main Jul 29, 2026
2 checks passed
@kiliczsh
kiliczsh deleted the v2-migration branch July 29, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant