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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +40 -0
- package/.github/workflows/code_health.yaml +21 -1
- package/.vscode/launch.json +1 -1
- package/README.md +141 -53
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -1
- package/dist/server.js +8 -19
- package/dist/server.js.map +1 -1
- package/dist/session.js +11 -12
- package/dist/session.js.map +1 -1
- package/dist/telemetry/constants.js +2 -2
- package/dist/telemetry/constants.js.map +1 -1
- package/dist/telemetry/device-id.js +20 -0
- package/dist/telemetry/device-id.js.map +1 -0
- package/dist/telemetry/telemetry.js +25 -28
- package/dist/telemetry/telemetry.js.map +1 -1
- package/dist/tools/atlas/atlasTool.js +32 -0
- package/dist/tools/atlas/atlasTool.js.map +1 -1
- package/dist/tools/atlas/metadata/connectCluster.js +21 -1
- package/dist/tools/atlas/metadata/connectCluster.js.map +1 -1
- package/dist/tools/atlas/read/inspectCluster.js +14 -3
- package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
- package/dist/tools/atlas/read/listClusters.js +15 -4
- package/dist/tools/atlas/read/listClusters.js.map +1 -1
- package/dist/tools/mongodb/mongodbTool.js +10 -0
- package/dist/tools/mongodb/mongodbTool.js.map +1 -1
- package/dist/tools/tool.js +34 -25
- package/dist/tools/tool.js.map +1 -1
- package/eslint.config.js +2 -2
- package/{jest.config.js → jest.config.ts} +1 -1
- package/package.json +6 -5
- package/scripts/apply.ts +1 -0
- package/scripts/filter.ts +3 -0
- package/src/common/atlas/apiClient.ts +2 -2
- package/src/logger.ts +3 -0
- package/src/server.ts +9 -24
- package/src/session.ts +10 -11
- package/src/telemetry/constants.ts +2 -2
- package/src/telemetry/device-id.ts +21 -0
- package/src/telemetry/eventCache.ts +1 -1
- package/src/telemetry/telemetry.ts +33 -30
- package/src/telemetry/types.ts +27 -29
- package/src/tools/atlas/atlasTool.ts +46 -1
- package/src/tools/atlas/metadata/connectCluster.ts +30 -1
- package/src/tools/atlas/read/inspectCluster.ts +28 -3
- package/src/tools/atlas/read/listClusters.ts +32 -4
- package/src/tools/mongodb/mongodbTool.ts +15 -1
- package/src/tools/tool.ts +50 -26
- package/tests/integration/helpers.ts +6 -8
- package/tests/integration/inMemoryTransport.ts +3 -3
- package/tests/integration/server.test.ts +4 -5
- package/tests/integration/tools/atlas/atlasHelpers.ts +3 -4
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +6 -3
- package/tests/unit/telemetry.test.ts +37 -9
- package/tsconfig.build.json +19 -0
- package/tsconfig.jest.json +1 -3
- package/tsconfig.json +5 -15
- 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.
|
|
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;
|
|
155
|
-
: "not set; before using any
|
|
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
|
-
|
|
73
|
-
|
|
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:
|
|
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
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Session } from "../session.js";
|
|
2
2
|
import { BaseEvent, CommonProperties } from "./types.js";
|
|
3
|
-
import {
|
|
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 (!
|
|
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:
|
|
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(
|
|
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(
|
|
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 {
|
package/src/telemetry/types.ts
CHANGED
|
@@ -8,49 +8,46 @@ export type TelemetryBoolSet = "true" | "false";
|
|
|
8
8
|
/**
|
|
9
9
|
* Base interface for all events
|
|
10
10
|
*/
|
|
11
|
-
export
|
|
11
|
+
export type TelemetryEvent<T> = {
|
|
12
12
|
timestamp: string;
|
|
13
13
|
source: "mdbmcp";
|
|
14
|
-
properties:
|
|
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
|
-
}
|
|
24
|
-
}
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type BaseEvent = TelemetryEvent<unknown>;
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
25
|
* Interface for tool events
|
|
28
26
|
*/
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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 =
|
|
82
|
+
const connectionString =
|
|
83
|
+
cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard || "N/A";
|
|
83
84
|
const mongoDBVersion = cluster.mongoDBVersion || "N/A";
|
|
84
|
-
|
|
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
|
}
|