mongodb-mcp-server 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +40 -0
  2. package/.github/workflows/code_health.yaml +21 -1
  3. package/.vscode/launch.json +1 -1
  4. package/README.md +141 -53
  5. package/dist/common/atlas/apiClient.js.map +1 -1
  6. package/dist/logger.js +3 -0
  7. package/dist/logger.js.map +1 -1
  8. package/dist/server.js +8 -19
  9. package/dist/server.js.map +1 -1
  10. package/dist/session.js +11 -12
  11. package/dist/session.js.map +1 -1
  12. package/dist/telemetry/constants.js +2 -2
  13. package/dist/telemetry/constants.js.map +1 -1
  14. package/dist/telemetry/device-id.js +20 -0
  15. package/dist/telemetry/device-id.js.map +1 -0
  16. package/dist/telemetry/telemetry.js +25 -28
  17. package/dist/telemetry/telemetry.js.map +1 -1
  18. package/dist/tools/atlas/atlasTool.js +32 -0
  19. package/dist/tools/atlas/atlasTool.js.map +1 -1
  20. package/dist/tools/atlas/metadata/connectCluster.js +21 -1
  21. package/dist/tools/atlas/metadata/connectCluster.js.map +1 -1
  22. package/dist/tools/atlas/read/inspectCluster.js +14 -3
  23. package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
  24. package/dist/tools/atlas/read/listClusters.js +15 -4
  25. package/dist/tools/atlas/read/listClusters.js.map +1 -1
  26. package/dist/tools/mongodb/mongodbTool.js +10 -0
  27. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  28. package/dist/tools/tool.js +34 -25
  29. package/dist/tools/tool.js.map +1 -1
  30. package/eslint.config.js +2 -2
  31. package/{jest.config.js → jest.config.ts} +1 -1
  32. package/package.json +6 -5
  33. package/scripts/apply.ts +1 -0
  34. package/scripts/filter.ts +3 -0
  35. package/src/common/atlas/apiClient.ts +2 -2
  36. package/src/logger.ts +3 -0
  37. package/src/server.ts +9 -24
  38. package/src/session.ts +10 -11
  39. package/src/telemetry/constants.ts +2 -2
  40. package/src/telemetry/device-id.ts +21 -0
  41. package/src/telemetry/eventCache.ts +1 -1
  42. package/src/telemetry/telemetry.ts +33 -30
  43. package/src/telemetry/types.ts +27 -29
  44. package/src/tools/atlas/atlasTool.ts +46 -1
  45. package/src/tools/atlas/metadata/connectCluster.ts +30 -1
  46. package/src/tools/atlas/read/inspectCluster.ts +28 -3
  47. package/src/tools/atlas/read/listClusters.ts +32 -4
  48. package/src/tools/mongodb/mongodbTool.ts +15 -1
  49. package/src/tools/tool.ts +50 -26
  50. package/tests/integration/helpers.ts +6 -8
  51. package/tests/integration/inMemoryTransport.ts +3 -3
  52. package/tests/integration/server.test.ts +4 -5
  53. package/tests/integration/tools/atlas/atlasHelpers.ts +3 -4
  54. package/tests/integration/tools/mongodb/mongodbHelpers.ts +6 -3
  55. package/tests/unit/telemetry.test.ts +37 -9
  56. package/tsconfig.build.json +19 -0
  57. package/tsconfig.jest.json +1 -3
  58. package/tsconfig.json +5 -15
  59. package/tsconfig.lint.json +0 -8
package/src/server.ts CHANGED
@@ -28,7 +28,7 @@ export class Server {
28
28
  constructor({ session, mcpServer, userConfig }: ServerOptions) {
29
29
  this.startTime = Date.now();
30
30
  this.session = session;
31
- this.telemetry = new Telemetry(session);
31
+ this.telemetry = new Telemetry(session, userConfig);
32
32
  this.mcpServer = mcpServer;
33
33
  this.userConfig = userConfig;
34
34
  }
@@ -107,7 +107,6 @@ export class Server {
107
107
  timestamp: new Date().toISOString(),
108
108
  source: "mdbmcp",
109
109
  properties: {
110
- ...this.telemetry.getCommonProperties(),
111
110
  result: "success",
112
111
  duration_ms: commandDuration,
113
112
  component: "server",
@@ -119,7 +118,7 @@ export class Server {
119
118
  if (command === "start") {
120
119
  event.properties.startup_time_ms = commandDuration;
121
120
  event.properties.read_only_mode = this.userConfig.readOnly || false;
122
- event.properties.disallowed_tools = this.userConfig.disabledTools || [];
121
+ event.properties.disabled_tools = this.userConfig.disabledTools || [];
123
122
  }
124
123
  if (command === "stop") {
125
124
  event.properties.runtime_duration_ms = Date.now() - this.startTime;
@@ -151,39 +150,25 @@ export class Server {
151
150
  telemetry: this.userConfig.telemetry,
152
151
  logPath: this.userConfig.logPath,
153
152
  connectionString: this.userConfig.connectionString
154
- ? "set; no explicit connect needed, use switch-connection tool to connect to a different connection if necessary"
155
- : "not set; before using any mongodb tool, you need to call the connect tool with a connection string",
153
+ ? "set; access to MongoDB tools are currently available to use"
154
+ : "not set; before using any MongoDB tool, you need to configure a connection string, alternatively you can setup MongoDB Atlas access, more info at 'https://github.com/mongodb-js/mongodb-mcp-server'.",
156
155
  connectOptions: this.userConfig.connectOptions,
156
+ atlas:
157
+ this.userConfig.apiClientId && this.userConfig.apiClientSecret
158
+ ? "set; MongoDB Atlas tools are currently available to use"
159
+ : "not set; MongoDB Atlas tools are currently unavailable, to have access to MongoDB Atlas tools like creating clusters or connecting to clusters make sure to setup credentials, more info at 'https://github.com/mongodb-js/mongodb-mcp-server'.",
157
160
  };
158
161
  return {
159
162
  contents: [
160
163
  {
161
164
  text: JSON.stringify(result),
165
+ mimeType: "application/json",
162
166
  uri: uri.href,
163
167
  },
164
168
  ],
165
169
  };
166
170
  }
167
171
  );
168
- if (this.userConfig.connectionString) {
169
- this.mcpServer.resource(
170
- "connection-string",
171
- "config://connection-string",
172
- {
173
- description: "Preconfigured connection string that will be used as a default in the `connect` tool",
174
- },
175
- (uri) => {
176
- return {
177
- contents: [
178
- {
179
- text: `Preconfigured connection string: ${this.userConfig.connectionString}`,
180
- uri: uri.href,
181
- },
182
- ],
183
- };
184
- }
185
- );
186
- }
187
172
  }
188
173
 
189
174
  private async validateConfig(): Promise<void> {
package/src/session.ts CHANGED
@@ -69,8 +69,8 @@ export class Session extends EventEmitter<{
69
69
  this.emit("disconnect");
70
70
  return;
71
71
  }
72
- try {
73
- await this.apiClient.deleteDatabaseUser({
72
+ void this.apiClient
73
+ .deleteDatabaseUser({
74
74
  params: {
75
75
  path: {
76
76
  groupId: this.connectedAtlasCluster.projectId,
@@ -78,16 +78,15 @@ export class Session extends EventEmitter<{
78
78
  databaseName: "admin",
79
79
  },
80
80
  },
81
+ })
82
+ .catch((err: unknown) => {
83
+ const error = err instanceof Error ? err : new Error(String(err));
84
+ logger.error(
85
+ LogId.atlasDeleteDatabaseUserFailure,
86
+ "atlas-connect-cluster",
87
+ `Error deleting previous database user: ${error.message}`
88
+ );
81
89
  });
82
- } catch (err: unknown) {
83
- const error = err instanceof Error ? err : new Error(String(err));
84
-
85
- logger.error(
86
- LogId.atlasDeleteDatabaseUserFailure,
87
- "atlas-connect-cluster",
88
- `Error deleting previous database user: ${error.message}`
89
- );
90
- }
91
90
  this.connectedAtlasCluster = undefined;
92
91
 
93
92
  this.emit("disconnect");
@@ -1,11 +1,11 @@
1
- import { getMachineIdSync } from "native-machine-id";
2
1
  import { packageInfo } from "../packageInfo.js";
3
2
  import { type CommonStaticProperties } from "./types.js";
3
+ import { getDeviceId } from "./device-id.js";
4
4
  /**
5
5
  * Machine-specific metadata formatted for telemetry
6
6
  */
7
7
  export const MACHINE_METADATA: CommonStaticProperties = {
8
- device_id: getMachineIdSync(),
8
+ device_id: getDeviceId(),
9
9
  mcp_server_version: packageInfo.version,
10
10
  mcp_server_name: packageInfo.mcpServerName,
11
11
  platform: process.platform,
@@ -0,0 +1,21 @@
1
+ import { createHmac } from "crypto";
2
+ import nodeMachineId from "node-machine-id";
3
+ import logger, { LogId } from "../logger.js";
4
+
5
+ export function getDeviceId(): string {
6
+ try {
7
+ const originalId = nodeMachineId.machineIdSync(true);
8
+ // Create a hashed format from the all uppercase version of the machine ID
9
+ // to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
10
+ const hmac = createHmac("sha256", originalId.toUpperCase());
11
+
12
+ /** This matches the message used to create the hashes in Atlas CLI */
13
+ const DEVICE_ID_HASH_MESSAGE = "atlascli";
14
+
15
+ hmac.update(DEVICE_ID_HASH_MESSAGE);
16
+ return hmac.digest("hex");
17
+ } catch (error) {
18
+ logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
19
+ return "unknown";
20
+ }
21
+ }
@@ -13,7 +13,7 @@ export class EventCache {
13
13
  private cache: LRUCache<number, BaseEvent>;
14
14
  private nextId = 0;
15
15
 
16
- private constructor() {
16
+ constructor() {
17
17
  this.cache = new LRUCache({
18
18
  max: EventCache.MAX_EVENTS,
19
19
  // Using FIFO eviction strategy for events
@@ -1,6 +1,6 @@
1
1
  import { Session } from "../session.js";
2
2
  import { BaseEvent, CommonProperties } from "./types.js";
3
- import { config } from "../config.js";
3
+ import { UserConfig } from "../config.js";
4
4
  import logger, { LogId } from "../logger.js";
5
5
  import { ApiClient } from "../common/atlas/apiClient.js";
6
6
  import { MACHINE_METADATA } from "./constants.js";
@@ -16,6 +16,7 @@ export class Telemetry {
16
16
 
17
17
  constructor(
18
18
  private readonly session: Session,
19
+ private readonly userConfig: UserConfig,
19
20
  private readonly eventCache: EventCache = EventCache.getInstance()
20
21
  ) {
21
22
  this.commonProperties = {
@@ -23,38 +24,14 @@ export class Telemetry {
23
24
  };
24
25
  }
25
26
 
26
- /**
27
- * Checks if telemetry is currently enabled
28
- * This is a method rather than a constant to capture runtime config changes
29
- *
30
- * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
31
- * by respecting the DO_NOT_TRACK environment variable
32
- */
33
- private static isTelemetryEnabled(): boolean {
34
- // Check if telemetry is explicitly disabled in config
35
- if (config.telemetry === "disabled") {
36
- return false;
37
- }
38
-
39
- const doNotTrack = process.env.DO_NOT_TRACK;
40
- if (doNotTrack) {
41
- const value = doNotTrack.toLowerCase();
42
- // Telemetry should be disabled if DO_NOT_TRACK is "1", "true", or "yes"
43
- if (value === "1" || value === "true" || value === "yes") {
44
- return false;
45
- }
46
- }
47
-
48
- return true;
49
- }
50
-
51
27
  /**
52
28
  * Emits events through the telemetry pipeline
53
29
  * @param events - The events to emit
54
30
  */
55
31
  public async emitEvents(events: BaseEvent[]): Promise<void> {
56
32
  try {
57
- if (!Telemetry.isTelemetryEnabled()) {
33
+ if (!this.isTelemetryEnabled()) {
34
+ logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
58
35
  return;
59
36
  }
60
37
 
@@ -75,10 +52,27 @@ export class Telemetry {
75
52
  mcp_client_name: this.session.agentRunner?.name,
76
53
  session_id: this.session.sessionId,
77
54
  config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
78
- config_connection_string: config.connectionString ? "true" : "false",
55
+ config_connection_string: this.userConfig.connectionString ? "true" : "false",
79
56
  };
80
57
  }
81
58
 
59
+ /**
60
+ * Checks if telemetry is currently enabled
61
+ * This is a method rather than a constant to capture runtime config changes
62
+ *
63
+ * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
64
+ * by respecting the DO_NOT_TRACK environment variable
65
+ */
66
+ public isTelemetryEnabled(): boolean {
67
+ // Check if telemetry is explicitly disabled in config
68
+ if (this.userConfig.telemetry === "disabled") {
69
+ return false;
70
+ }
71
+
72
+ const doNotTrack = "DO_NOT_TRACK" in process.env;
73
+ return !doNotTrack;
74
+ }
75
+
82
76
  /**
83
77
  * Attempts to emit events through authenticated and unauthenticated clients
84
78
  * Falls back to caching if both attempts fail
@@ -96,7 +90,11 @@ export class Telemetry {
96
90
  const result = await this.sendEvents(this.session.apiClient, allEvents);
97
91
  if (result.success) {
98
92
  this.eventCache.clearEvents();
99
- logger.debug(LogId.telemetryEmitSuccess, "telemetry", `Sent ${allEvents.length} events successfully`);
93
+ logger.debug(
94
+ LogId.telemetryEmitSuccess,
95
+ "telemetry",
96
+ `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
97
+ );
100
98
  return;
101
99
  }
102
100
 
@@ -113,7 +111,12 @@ export class Telemetry {
113
111
  */
114
112
  private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
115
113
  try {
116
- await client.sendEvents(events);
114
+ await client.sendEvents(
115
+ events.map((event) => ({
116
+ ...event,
117
+ properties: { ...this.getCommonProperties(), ...event.properties },
118
+ }))
119
+ );
117
120
  return { success: true };
118
121
  } catch (error) {
119
122
  return {
@@ -8,49 +8,46 @@ export type TelemetryBoolSet = "true" | "false";
8
8
  /**
9
9
  * Base interface for all events
10
10
  */
11
- export interface Event {
11
+ export type TelemetryEvent<T> = {
12
12
  timestamp: string;
13
13
  source: "mdbmcp";
14
- properties: Record<string, unknown>;
15
- }
16
-
17
- export interface BaseEvent extends Event {
18
- properties: CommonProperties & {
14
+ properties: T & {
19
15
  component: string;
20
16
  duration_ms: number;
21
17
  result: TelemetryResult;
22
18
  category: string;
23
- } & Event["properties"];
24
- }
19
+ };
20
+ };
21
+
22
+ export type BaseEvent = TelemetryEvent<unknown>;
25
23
 
26
24
  /**
27
25
  * Interface for tool events
28
26
  */
29
- export interface ToolEvent extends BaseEvent {
30
- properties: {
31
- command: string;
32
- error_code?: string;
33
- error_type?: string;
34
- project_id?: string;
35
- org_id?: string;
36
- cluster_name?: string;
37
- is_atlas?: boolean;
38
- } & BaseEvent["properties"];
39
- }
27
+ export type ToolEventProperties = {
28
+ command: string;
29
+ error_code?: string;
30
+ error_type?: string;
31
+ project_id?: string;
32
+ org_id?: string;
33
+ cluster_name?: string;
34
+ is_atlas?: boolean;
35
+ };
40
36
 
37
+ export type ToolEvent = TelemetryEvent<ToolEventProperties>;
41
38
  /**
42
39
  * Interface for server events
43
40
  */
44
- export interface ServerEvent extends BaseEvent {
45
- properties: {
46
- command: ServerCommand;
47
- reason?: string;
48
- startup_time_ms?: number;
49
- runtime_duration_ms?: number;
50
- read_only_mode?: boolean;
51
- disabled_tools?: string[];
52
- } & BaseEvent["properties"];
53
- }
41
+ export type ServerEventProperties = {
42
+ command: ServerCommand;
43
+ reason?: string;
44
+ startup_time_ms?: number;
45
+ runtime_duration_ms?: number;
46
+ read_only_mode?: boolean;
47
+ disabled_tools?: string[];
48
+ };
49
+
50
+ export type ServerEvent = TelemetryEvent<ServerEventProperties>;
54
51
 
55
52
  /**
56
53
  * Interface for static properties, they can be fetched once and reused.
@@ -69,6 +66,7 @@ export type CommonStaticProperties = {
69
66
  * Common properties for all events that might change.
70
67
  */
71
68
  export type CommonProperties = {
69
+ device_id?: string;
72
70
  mcp_client_version?: string;
73
71
  mcp_client_name?: string;
74
72
  config_atlas_auth?: TelemetryBoolSet;
@@ -1,4 +1,7 @@
1
- import { ToolBase, ToolCategory } from "../tool.js";
1
+ import { ToolBase, ToolCategory, TelemetryToolMetadata } from "../tool.js";
2
+ import { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import logger, { LogId } from "../../logger.js";
4
+ import { z } from "zod";
2
5
 
3
6
  export abstract class AtlasToolBase extends ToolBase {
4
7
  protected category: ToolCategory = "atlas";
@@ -9,4 +12,46 @@ export abstract class AtlasToolBase extends ToolBase {
9
12
  }
10
13
  return super.verifyAllowed();
11
14
  }
15
+
16
+ /**
17
+ *
18
+ * Resolves the tool metadata from the arguments passed to the tool
19
+ *
20
+ * @param args - The arguments passed to the tool
21
+ * @returns The tool metadata
22
+ */
23
+ protected resolveTelemetryMetadata(
24
+ ...args: Parameters<ToolCallback<typeof this.argsShape>>
25
+ ): TelemetryToolMetadata {
26
+ const toolMetadata: TelemetryToolMetadata = {};
27
+ if (!args.length) {
28
+ return toolMetadata;
29
+ }
30
+
31
+ // Create a typed parser for the exact shape we expect
32
+ const argsShape = z.object(this.argsShape);
33
+ const parsedResult = argsShape.safeParse(args[0]);
34
+
35
+ if (!parsedResult.success) {
36
+ logger.debug(
37
+ LogId.telemetryMetadataError,
38
+ "tool",
39
+ `Error parsing tool arguments: ${parsedResult.error.message}`
40
+ );
41
+ return toolMetadata;
42
+ }
43
+
44
+ const data = parsedResult.data;
45
+
46
+ // Extract projectId using type guard
47
+ if ("projectId" in data && typeof data.projectId === "string" && data.projectId.trim() !== "") {
48
+ toolMetadata.projectId = data.projectId;
49
+ }
50
+
51
+ // Extract orgId using type guard
52
+ if ("orgId" in data && typeof data.orgId === "string" && data.orgId.trim() !== "") {
53
+ toolMetadata.orgId = data.orgId;
54
+ }
55
+ return toolMetadata;
56
+ }
12
57
  }
@@ -4,6 +4,7 @@ import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { randomBytes } from "crypto";
6
6
  import { promisify } from "util";
7
+ import logger, { LogId } from "../../../logger.js";
7
8
 
8
9
  const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours
9
10
 
@@ -15,6 +16,10 @@ async function generateSecurePassword(): Promise<string> {
15
16
  return pass;
16
17
  }
17
18
 
19
+ function sleep(ms: number): Promise<void> {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+
18
23
  export class ConnectClusterTool extends AtlasToolBase {
19
24
  protected name = "atlas-connect-cluster";
20
25
  protected description = "Connect to MongoDB Atlas cluster";
@@ -100,7 +105,31 @@ export class ConnectClusterTool extends AtlasToolBase {
100
105
  cn.searchParams.set("authSource", "admin");
101
106
  const connectionString = cn.toString();
102
107
 
103
- await this.session.connectToMongoDB(connectionString, this.config.connectOptions);
108
+ let lastError: Error | undefined = undefined;
109
+
110
+ for (let i = 0; i < 20; i++) {
111
+ try {
112
+ await this.session.connectToMongoDB(connectionString, this.config.connectOptions);
113
+ lastError = undefined;
114
+ break;
115
+ } catch (err: unknown) {
116
+ const error = err instanceof Error ? err : new Error(String(err));
117
+
118
+ lastError = error;
119
+
120
+ logger.debug(
121
+ LogId.atlasConnectFailure,
122
+ "atlas-connect-cluster",
123
+ `error connecting to cluster: ${error.message}`
124
+ );
125
+
126
+ await sleep(500); // wait for 500ms before retrying
127
+ }
128
+ }
129
+
130
+ if (lastError) {
131
+ throw lastError;
132
+ }
104
133
 
105
134
  return {
106
135
  content: [
@@ -31,13 +31,38 @@ export class InspectClusterTool extends AtlasToolBase {
31
31
  throw new Error("Cluster not found");
32
32
  }
33
33
 
34
+ const regionConfigs = (cluster.replicationSpecs || [])
35
+ .map(
36
+ (replicationSpec) =>
37
+ (replicationSpec.regionConfigs || []) as {
38
+ providerName: string;
39
+ electableSpecs?: {
40
+ instanceSize: string;
41
+ };
42
+ readOnlySpecs?: {
43
+ instanceSize: string;
44
+ };
45
+ }[]
46
+ )
47
+ .flat()
48
+ .map((regionConfig) => {
49
+ return {
50
+ providerName: regionConfig.providerName,
51
+ instanceSize: regionConfig.electableSpecs?.instanceSize || regionConfig.readOnlySpecs?.instanceSize,
52
+ };
53
+ });
54
+
55
+ const instanceSize = (regionConfigs.length <= 0 ? undefined : regionConfigs[0].instanceSize) || "UNKNOWN";
56
+
57
+ const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED";
58
+
34
59
  return {
35
60
  content: [
36
61
  {
37
62
  type: "text",
38
- text: `Cluster Name | State | MongoDB Version | Connection String
39
- ----------------|----------------|----------------|----------------|----------------
40
- ${cluster.name} | ${cluster.stateName} | ${cluster.mongoDBVersion || "N/A"} | ${cluster.connectionStrings?.standard || "N/A"}`,
63
+ text: `Cluster Name | Cluster Type | Tier | State | MongoDB Version | Connection String
64
+ ----------------|----------------|----------------|----------------|----------------|----------------
65
+ ${cluster.name} | ${clusterInstanceType} | ${clusterInstanceType == "DEDICATED" ? instanceSize : "N/A"} | ${cluster.stateName} | ${cluster.mongoDBVersion || "N/A"} | ${cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard || "N/A"}`,
41
66
  },
42
67
  ],
43
68
  };
@@ -79,9 +79,37 @@ ${rows}`,
79
79
  }
80
80
  const rows = clusters.results
81
81
  .map((cluster) => {
82
- const connectionString = cluster.connectionStrings?.standard || "N/A";
82
+ const connectionString =
83
+ cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard || "N/A";
83
84
  const mongoDBVersion = cluster.mongoDBVersion || "N/A";
84
- return `${cluster.name} | ${cluster.stateName} | ${mongoDBVersion} | ${connectionString}`;
85
+ const regionConfigs = (cluster.replicationSpecs || [])
86
+ .map(
87
+ (replicationSpec) =>
88
+ (replicationSpec.regionConfigs || []) as {
89
+ providerName: string;
90
+ electableSpecs?: {
91
+ instanceSize: string;
92
+ };
93
+ readOnlySpecs?: {
94
+ instanceSize: string;
95
+ };
96
+ }[]
97
+ )
98
+ .flat()
99
+ .map((regionConfig) => {
100
+ return {
101
+ providerName: regionConfig.providerName,
102
+ instanceSize:
103
+ regionConfig.electableSpecs?.instanceSize || regionConfig.readOnlySpecs?.instanceSize,
104
+ };
105
+ });
106
+
107
+ const instanceSize =
108
+ (regionConfigs.length <= 0 ? undefined : regionConfigs[0].instanceSize) || "UNKNOWN";
109
+
110
+ const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED";
111
+
112
+ return `${cluster.name} | ${clusterInstanceType} | ${clusterInstanceType == "DEDICATED" ? instanceSize : "N/A"} | ${cluster.stateName} | ${mongoDBVersion} | ${connectionString}`;
85
113
  })
86
114
  .join("\n");
87
115
  return {
@@ -92,8 +120,8 @@ ${rows}`,
92
120
  },
93
121
  {
94
122
  type: "text",
95
- text: `Cluster Name | State | MongoDB Version | Connection String
96
- ----------------|----------------|----------------|----------------|----------------
123
+ text: `Cluster Name | Cluster Type | Tier | State | MongoDB Version | Connection String
124
+ ----------------|----------------|----------------|----------------|----------------|----------------
97
125
  ${rows}`,
98
126
  },
99
127
  ],
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { ToolArgs, ToolBase, ToolCategory } from "../tool.js";
2
+ import { ToolArgs, ToolBase, ToolCategory, TelemetryToolMetadata } from "../tool.js";
3
3
  import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
4
4
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { ErrorCodes, MongoDBError } from "../../errors.js";
@@ -73,4 +73,18 @@ export abstract class MongoDBToolBase extends ToolBase {
73
73
  protected connectToMongoDB(connectionString: string): Promise<void> {
74
74
  return this.session.connectToMongoDB(connectionString, this.config.connectOptions);
75
75
  }
76
+
77
+ protected resolveTelemetryMetadata(
78
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
79
+ args: ToolArgs<typeof this.argsShape>
80
+ ): TelemetryToolMetadata {
81
+ const metadata: TelemetryToolMetadata = {};
82
+
83
+ // Add projectId to the metadata if running a MongoDB operation to an Atlas cluster
84
+ if (this.session.connectedAtlasCluster?.projectId) {
85
+ metadata.projectId = this.session.connectedAtlasCluster.projectId;
86
+ }
87
+
88
+ return metadata;
89
+ }
76
90
  }