Skip to content

feat: add OIDC authentication support for atlas-connect-cluster#1198

Draft
gmishkin wants to merge 3 commits into
mainfrom
geoff/atlas-connect-oidc
Draft

feat: add OIDC authentication support for atlas-connect-cluster#1198
gmishkin wants to merge 3 commits into
mainfrom
geoff/atlas-connect-oidc

Conversation

@gmishkin

Copy link
Copy Markdown
Collaborator

Summary

Adds mongodb-oidc as an alternative authentication mechanism for atlas-connect-cluster, controlled by two new server-level config options:

  • MDB_MCP_ATLAS_AUTH_MECHANISM=mongodb-oidc — opt in to OIDC instead of temporary user creation
  • MDB_MCP_ATLAS_OIDC_MECHANISM_PROPERTIES — optional authMechanismProperties string for workload identity federation (e.g. ENVIRONMENT:k8s,TOKEN_RESOURCE:<audience>)

When OIDC is enabled, the tool skips creating a temporary Atlas database user entirely, allowing deployments with lower-privilege Atlas API credentials (no user management permissions required — Project Read Only + Project IP Access List Admin is sufficient).

Supports both:

  • Workforce identity federation: human interactive browser/device-code flows (no authMechanismProperties needed)
  • Workload identity federation: non-interactive cloud identity (Azure Managed Identity, GCP SA, Kubernetes ServiceAccount) via MDB_MCP_ATLAS_OIDC_MECHANISM_PROPERTIES

Editor's note: See here for an acknowledgment of the limitations of the workload identity federation support.

Design decisions and tradeoffs

Auth mechanism is server-level config, not a tool argument.
An earlier approach exposed authMechanism as a tool argument. We moved it to server config because the LLM agent has no way to know whether a given Atlas project has OIDC configured — that's deployment-specific knowledge the operator holds. A single MCP server deployment is already implicitly scoped to one Atlas org (the API credentials are org-scoped), and OIDC availability is also org/federation-scoped, so a server-level toggle is the right boundary.

authMechanismProperties is also server-only for the same reason: which cloud environment the server is running in (ENVIRONMENT:azure, ENVIRONMENT:k8s, etc.) is a property of the deployment, not something the agent should determine at call time.

Tool-level authMechanism: "username-password" override retained as escape hatch.
If the server is configured for OIDC but the OIDC identity lacks sufficient database access on a specific cluster (wrong cluster scope or insufficient roles), the agent's only recourse without this override would be a server restart. The tool now accepts an optional authMechanism: "username-password" argument that forces the temp-user path for that connection. The description is intentionally restrictive: agents are instructed only to set this after observing authorization errors on a previous OIDC-authenticated connection to the cluster.

No preflight check for OIDC user access.
We considered checking whether the OIDC identity has appropriate cluster scopes and roles before connecting, but there is no clean way to do this: the check would require additional Atlas API calls, and authorization errors only surface through actual database operations after the connection is established. The config description documents the requirement that operators ensure their OIDC database user in Atlas has appropriate cluster scopes and roles.

Fallback to temp-user for pre-7.0 clusters.
When atlasAuthMechanism=mongodb-oidc is set but the target cluster is detected as running MongoDB < 7.0 (which does not support OIDC), the tool automatically falls back to temporary user creation and includes a note in the response. This requires the Atlas API credentials to have user management permissions for pre-7.0 clusters in a mixed-version org.

AtlasClusterConnectionInfo refactored to a discriminated union.
Editor's note: This one is not super interesting. Check the edit history of this description to see the details

IP access list check is unchanged for OIDC.
Editor's note: I think this should be considered for https://jira.mongodb.org/browse/MCP-334

We briefly considered skipping ensureCurrentIpInAccessList in OIDC mode (since lower-privilege credentials can't write to the access list). However, reading the access list and the api/private/ipinfo endpoint both work with Project IP Access List Admin, which is a reasonable permission to grant alongside Project Read Only for an OIDC deployment. Skipping the check was reverted — operators should grant Project IP Access List Admin to their service account.

Test plan

  • pnpm run check:types passes
  • pnpm run test:local passes (pre-existing failures in exportsManager, dropIndex, collectionIndexes are unrelated)
  • Manual test: Atlas cluster with OIDC configured, MDB_MCP_ATLAS_AUTH_MECHANISM=mongodb-oidc — connection succeeds without creating a DB user
  • Manual test: pre-7.0 cluster with OIDC config — falls back to temp user with notice in response
  • Manual test: OIDC connection followed by auth error, then retry with authMechanism: "username-password" — switches to temp user

Adds mongodb-oidc as an alternative authentication mechanism for the
atlas-connect-cluster tool, controlled via server-level config
(MDB_MCP_ATLAS_AUTH_MECHANISM=mongodb-oidc). When enabled, the tool
connects using OIDC rather than creating a temporary Atlas database user,
allowing deployments with lower-privilege Atlas API credentials.

Supports both workforce identity federation (human browser/device-code
flows) and workload identity federation (non-interactive cloud identity,
e.g. Azure Managed Identity, GCP, Kubernetes) via the companion
MDB_MCP_ATLAS_OIDC_MECHANISM_PROPERTIES config option.
@coveralls

coveralls commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 26189473402

Warning

No base build found for commit 458b3c8 on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 78.953%

Details

  • Patch coverage: 28 uncovered changes across 1 file (9 of 37 lines covered, 24.32%).

Uncovered Changes

File Changed Covered %
src/tools/atlas/connect/connectCluster.ts 34 6 17.65%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 4010
Covered Lines: 3371
Line Coverage: 84.06%
Relevant Branches: 2542
Covered Branches: 1802
Branch Coverage: 70.89%
Branches in Coverage %: Yes
Coverage Strength: 160.49 hits per line

💛 - Coveralls

Address review feedback on #1189:
- Use ConnectionString from mongodb-connection-string-url instead of
  new URL() to correctly handle multi-host connection strings
- Use semver.coerce/lt instead of manual parseInt for MongoDB version
  comparison to be robust against MongoDB 10+
private async prepareClusterConnectionOIDC(
projectId: string,
clusterName: string,
connectionType: "standard" | "private" | "privateEndpoint" | undefined = "standard"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addaleax when you said deduplicating on the other PR is this what you were getting at?

diff --git a/src/common/atlas/cluster.ts b/src/common/atlas/cluster.ts
index ...  100644
--- a/src/common/atlas/cluster.ts
+++ b/src/common/atlas/cluster.ts
@@ -129,8 +129,10 @@ }
 /**
  * Returns a connection string for the specified connectionType.
  * For "privateEndpoint", it returns the first private endpoint connection string available.
  */
+export type ClusterConnectionType = "standard" | "private" | "privateEndpoint";
+
 export function getConnectionString(
     connectionStrings: ClusterConnectionStrings,
-    connectionType: "standard" | "private" | "privateEndpoint"
+    connectionType: ClusterConnectionType
 ): string | undefined {

diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts
index ...  100644
--- a/src/tools/atlas/connect/connectCluster.ts
+++ b/src/tools/atlas/connect/connectCluster.ts
@@ -7,7 +7,7 @@
-import { getConnectionString, inspectCluster } from "../../../common/atlas/cluster.js";
+import { getConnectionString, inspectCluster, type ClusterConnectionType } from "../../../common/atlas/cluster.js";

-    ): Promise<AtlasClusterConnectionInfo> {  // prepareClusterConnection, line 89
-        connectionType: "standard" | "private" | "privateEndpoint" | undefined = "standard"
+        connectionType: ClusterConnectionType | undefined = "standard"

-    ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo } | null> {  // prepareClusterConnectionOIDC, line 157
-        connectionType: "standard" | "private" | "privateEndpoint" | undefined = "standard"
+        connectionType: ClusterConnectionType | undefined = "standard"

Extract oidcDeviceFlowContent() from connectionErrorHandler and reuse
it in the Atlas connect cluster polling path, removing the duplicate
inline message and the redundant "call this tool again" instruction.
@github-actions

Copy link
Copy Markdown
Contributor

This PR has gone 30 days without any activity and meets the project's definition of "stale". This will be auto-closed if there is no new activity over the next 30 days. If the issue is still relevant and active, you can simply comment with a "bump" to keep it open, or add the label "not_stale". Thanks for keeping our repository healthy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants