mongodb-mcp-server 0.0.5 → 0.0.7
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/CODEOWNERS +3 -0
- package/.github/dependabot.yml +10 -0
- package/.github/workflows/code_health.yaml +9 -27
- package/.github/workflows/code_health_fork.yaml +106 -0
- package/.github/workflows/codeql.yml +34 -0
- package/.github/workflows/lint.yml +37 -0
- package/.github/workflows/prepare_release.yaml +12 -4
- package/.github/workflows/publish.yaml +6 -3
- package/.prettierrc.json +1 -1
- package/README.md +16 -0
- package/dist/common/atlas/apiClient.js +1 -5
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/logger.js +20 -1
- package/dist/logger.js.map +1 -1
- package/dist/server.js +44 -3
- package/dist/server.js.map +1 -1
- package/dist/session.js +50 -5
- package/dist/session.js.map +1 -1
- package/dist/telemetry/constants.js.map +1 -1
- package/dist/telemetry/telemetry.js +7 -6
- package/dist/telemetry/telemetry.js.map +1 -1
- package/dist/tools/atlas/{createAccessList.js → create/createAccessList.js} +1 -1
- package/dist/tools/atlas/create/createAccessList.js.map +1 -0
- package/dist/tools/atlas/{createDBUser.js → create/createDBUser.js} +1 -1
- package/dist/tools/atlas/create/createDBUser.js.map +1 -0
- package/dist/tools/atlas/{createFreeCluster.js → create/createFreeCluster.js} +5 -2
- package/dist/tools/atlas/create/createFreeCluster.js.map +1 -0
- package/dist/tools/atlas/{createProject.js → create/createProject.js} +1 -1
- package/dist/tools/atlas/create/createProject.js.map +1 -0
- package/dist/tools/atlas/metadata/connectCluster.js +97 -0
- package/dist/tools/atlas/metadata/connectCluster.js.map +1 -0
- package/dist/tools/atlas/{inspectAccessList.js → read/inspectAccessList.js} +1 -1
- package/dist/tools/atlas/read/inspectAccessList.js.map +1 -0
- package/dist/tools/atlas/{inspectCluster.js → read/inspectCluster.js} +1 -1
- package/dist/tools/atlas/read/inspectCluster.js.map +1 -0
- package/dist/tools/atlas/{listClusters.js → read/listClusters.js} +1 -1
- package/dist/tools/atlas/read/listClusters.js.map +1 -0
- package/dist/tools/atlas/{listDBUsers.js → read/listDBUsers.js} +1 -1
- package/dist/tools/atlas/read/listDBUsers.js.map +1 -0
- package/dist/tools/atlas/{listOrgs.js → read/listOrgs.js} +1 -1
- package/dist/tools/atlas/read/listOrgs.js.map +1 -0
- package/dist/tools/atlas/{listProjects.js → read/listProjects.js} +1 -1
- package/dist/tools/atlas/read/listProjects.js.map +1 -0
- package/dist/tools/atlas/tools.js +12 -10
- package/dist/tools/atlas/tools.js.map +1 -1
- package/dist/tools/mongodb/create/insertMany.js +1 -1
- package/dist/tools/mongodb/create/insertMany.js.map +1 -1
- package/dist/tools/mongodb/delete/deleteMany.js +1 -2
- package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
- package/dist/tools/mongodb/metadata/connect.js +59 -71
- package/dist/tools/mongodb/metadata/connect.js.map +1 -1
- package/dist/tools/mongodb/mongodbTool.js +37 -30
- package/dist/tools/mongodb/mongodbTool.js.map +1 -1
- package/dist/tools/mongodb/read/aggregate.js +1 -1
- package/dist/tools/mongodb/read/aggregate.js.map +1 -1
- package/dist/tools/mongodb/read/count.js +1 -2
- package/dist/tools/mongodb/read/count.js.map +1 -1
- package/dist/tools/mongodb/read/find.js +2 -4
- package/dist/tools/mongodb/read/find.js.map +1 -1
- package/dist/tools/mongodb/tools.js +4 -2
- package/dist/tools/mongodb/tools.js.map +1 -1
- package/dist/tools/mongodb/update/updateMany.js +2 -4
- package/dist/tools/mongodb/update/updateMany.js.map +1 -1
- package/dist/tools/tool.js +30 -6
- package/dist/tools/tool.js.map +1 -1
- package/package.json +3 -3
- package/src/common/atlas/apiClient.ts +3 -11
- package/src/config.ts +13 -8
- package/src/errors.ts +1 -1
- package/src/index.ts +3 -3
- package/src/logger.ts +26 -1
- package/src/server.ts +60 -5
- package/src/session.ts +69 -6
- package/src/telemetry/constants.ts +2 -2
- package/src/telemetry/telemetry.ts +9 -21
- package/src/telemetry/types.ts +28 -11
- package/src/tools/atlas/{createAccessList.ts → create/createAccessList.ts} +2 -2
- package/src/tools/atlas/{createDBUser.ts → create/createDBUser.ts} +3 -3
- package/src/tools/atlas/{createFreeCluster.ts → create/createFreeCluster.ts} +7 -4
- package/src/tools/atlas/{createProject.ts → create/createProject.ts} +3 -3
- package/src/tools/atlas/metadata/connectCluster.ts +114 -0
- package/src/tools/atlas/{inspectAccessList.ts → read/inspectAccessList.ts} +2 -2
- package/src/tools/atlas/{inspectCluster.ts → read/inspectCluster.ts} +3 -3
- package/src/tools/atlas/{listClusters.ts → read/listClusters.ts} +3 -3
- package/src/tools/atlas/{listDBUsers.ts → read/listDBUsers.ts} +3 -3
- package/src/tools/atlas/{listOrgs.ts → read/listOrgs.ts} +2 -2
- package/src/tools/atlas/{listProjects.ts → read/listProjects.ts} +3 -3
- package/src/tools/atlas/tools.ts +12 -10
- package/src/tools/mongodb/create/insertMany.ts +1 -1
- package/src/tools/mongodb/delete/deleteMany.ts +1 -2
- package/src/tools/mongodb/metadata/connect.ts +78 -75
- package/src/tools/mongodb/mongodbTool.ts +40 -30
- package/src/tools/mongodb/read/aggregate.ts +1 -1
- package/src/tools/mongodb/read/count.ts +1 -2
- package/src/tools/mongodb/read/find.ts +2 -4
- package/src/tools/mongodb/tools.ts +4 -2
- package/src/tools/mongodb/update/updateMany.ts +2 -4
- package/src/tools/tool.ts +40 -13
- package/tests/integration/helpers.ts +13 -3
- package/tests/integration/server.test.ts +46 -20
- package/tests/integration/tools/atlas/atlasHelpers.ts +7 -5
- package/tests/integration/tools/atlas/clusters.test.ts +68 -4
- package/tests/integration/tools/mongodb/metadata/connect.test.ts +85 -100
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +32 -21
- package/tests/unit/telemetry.test.ts +200 -0
- package/dist/tools/atlas/createAccessList.js.map +0 -1
- package/dist/tools/atlas/createDBUser.js.map +0 -1
- package/dist/tools/atlas/createFreeCluster.js.map +0 -1
- package/dist/tools/atlas/createProject.js.map +0 -1
- package/dist/tools/atlas/inspectAccessList.js.map +0 -1
- package/dist/tools/atlas/inspectCluster.js.map +0 -1
- package/dist/tools/atlas/listClusters.js.map +0 -1
- package/dist/tools/atlas/listDBUsers.js.map +0 -1
- package/dist/tools/atlas/listOrgs.js.map +0 -1
- package/dist/tools/atlas/listProjects.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import logger from "./logger.js";
|
|
5
|
-
import { mongoLogId } from "mongodb-log-writer";
|
|
4
|
+
import logger, { LogId } from "./logger.js";
|
|
6
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
6
|
import { config } from "./config.js";
|
|
8
7
|
import { Session } from "./session.js";
|
|
@@ -19,6 +18,7 @@ try {
|
|
|
19
18
|
name: packageInfo.mcpServerName,
|
|
20
19
|
version: packageInfo.version,
|
|
21
20
|
});
|
|
21
|
+
|
|
22
22
|
const server = new Server({
|
|
23
23
|
mcpServer,
|
|
24
24
|
session,
|
|
@@ -29,6 +29,6 @@ try {
|
|
|
29
29
|
|
|
30
30
|
await server.connect(transport);
|
|
31
31
|
} catch (error: unknown) {
|
|
32
|
-
logger.emergency(
|
|
32
|
+
logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`);
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
package/src/logger.ts
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
|
-
import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
|
|
2
|
+
import { mongoLogId, MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
|
|
3
3
|
import redact from "mongodb-redact";
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
|
|
7
7
|
export type LogLevel = LoggingMessageNotification["params"]["level"];
|
|
8
8
|
|
|
9
|
+
export const LogId = {
|
|
10
|
+
serverStartFailure: mongoLogId(1_000_001),
|
|
11
|
+
serverInitialized: mongoLogId(1_000_002),
|
|
12
|
+
|
|
13
|
+
atlasCheckCredentials: mongoLogId(1_001_001),
|
|
14
|
+
atlasDeleteDatabaseUserFailure: mongoLogId(1_001_002),
|
|
15
|
+
|
|
16
|
+
telemetryDisabled: mongoLogId(1_002_001),
|
|
17
|
+
telemetryEmitFailure: mongoLogId(1_002_002),
|
|
18
|
+
telemetryEmitStart: mongoLogId(1_002_003),
|
|
19
|
+
telemetryEmitSuccess: mongoLogId(1_002_004),
|
|
20
|
+
|
|
21
|
+
toolExecute: mongoLogId(1_003_001),
|
|
22
|
+
toolExecuteFailure: mongoLogId(1_003_002),
|
|
23
|
+
toolDisabled: mongoLogId(1_003_003),
|
|
24
|
+
|
|
25
|
+
mongodbConnectFailure: mongoLogId(1_004_001),
|
|
26
|
+
mongodbDisconnectFailure: mongoLogId(1_004_002),
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
9
29
|
abstract class LoggerBase {
|
|
10
30
|
abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
|
|
11
31
|
|
|
@@ -106,6 +126,11 @@ class McpLogger extends LoggerBase {
|
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
log(level: LogLevel, _: MongoLogId, context: string, message: string): void {
|
|
129
|
+
// Only log if the server is connected
|
|
130
|
+
if (!this.server?.isConnected()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
void this.server.server.sendLoggingMessage({
|
|
110
135
|
level,
|
|
111
136
|
data: `[${context}]: ${message}`,
|
package/src/server.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { Session } from "./session.js";
|
|
|
3
3
|
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
4
4
|
import { AtlasTools } from "./tools/atlas/tools.js";
|
|
5
5
|
import { MongoDbTools } from "./tools/mongodb/tools.js";
|
|
6
|
-
import logger, { initializeLogger } from "./logger.js";
|
|
7
|
-
import { mongoLogId } from "mongodb-log-writer";
|
|
6
|
+
import logger, { initializeLogger, LogId } from "./logger.js";
|
|
8
7
|
import { ObjectId } from "mongodb";
|
|
9
8
|
import { Telemetry } from "./telemetry/telemetry.js";
|
|
10
9
|
import { UserConfig } from "./config.js";
|
|
@@ -23,7 +22,7 @@ export class Server {
|
|
|
23
22
|
public readonly session: Session;
|
|
24
23
|
private readonly mcpServer: McpServer;
|
|
25
24
|
private readonly telemetry: Telemetry;
|
|
26
|
-
|
|
25
|
+
public readonly userConfig: UserConfig;
|
|
27
26
|
private readonly startTime: number;
|
|
28
27
|
|
|
29
28
|
constructor({ session, mcpServer, userConfig }: ServerOptions) {
|
|
@@ -34,8 +33,9 @@ export class Server {
|
|
|
34
33
|
this.userConfig = userConfig;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
async connect(transport: Transport) {
|
|
36
|
+
async connect(transport: Transport): Promise<void> {
|
|
38
37
|
this.mcpServer.server.registerCapabilities({ logging: {} });
|
|
38
|
+
|
|
39
39
|
this.registerTools();
|
|
40
40
|
this.registerResources();
|
|
41
41
|
|
|
@@ -71,7 +71,7 @@ export class Server {
|
|
|
71
71
|
this.session.sessionId = new ObjectId().toString();
|
|
72
72
|
|
|
73
73
|
logger.info(
|
|
74
|
-
|
|
74
|
+
LogId.serverInitialized,
|
|
75
75
|
"server",
|
|
76
76
|
`Server started with transport ${transport.constructor.name} and agent runner ${this.session.agentRunner?.name}`
|
|
77
77
|
);
|
|
@@ -88,6 +88,8 @@ export class Server {
|
|
|
88
88
|
const closeTime = Date.now();
|
|
89
89
|
this.emitServerEvent("stop", Date.now() - closeTime, error);
|
|
90
90
|
};
|
|
91
|
+
|
|
92
|
+
await this.validateConfig();
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
async close(): Promise<void> {
|
|
@@ -116,6 +118,8 @@ export class Server {
|
|
|
116
118
|
|
|
117
119
|
if (command === "start") {
|
|
118
120
|
event.properties.startup_time_ms = commandDuration;
|
|
121
|
+
event.properties.read_only_mode = this.userConfig.readOnly || false;
|
|
122
|
+
event.properties.disallowed_tools = this.userConfig.disabledTools || [];
|
|
119
123
|
}
|
|
120
124
|
if (command === "stop") {
|
|
121
125
|
event.properties.runtime_duration_ms = Date.now() - this.startTime;
|
|
@@ -135,6 +139,32 @@ export class Server {
|
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
private registerResources() {
|
|
142
|
+
this.mcpServer.resource(
|
|
143
|
+
"config",
|
|
144
|
+
"config://config",
|
|
145
|
+
{
|
|
146
|
+
description:
|
|
147
|
+
"Server configuration, supplied by the user either as environment variables or as startup arguments",
|
|
148
|
+
},
|
|
149
|
+
(uri) => {
|
|
150
|
+
const result = {
|
|
151
|
+
telemetry: this.userConfig.telemetry,
|
|
152
|
+
logPath: this.userConfig.logPath,
|
|
153
|
+
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",
|
|
156
|
+
connectOptions: this.userConfig.connectOptions,
|
|
157
|
+
};
|
|
158
|
+
return {
|
|
159
|
+
contents: [
|
|
160
|
+
{
|
|
161
|
+
text: JSON.stringify(result),
|
|
162
|
+
uri: uri.href,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
);
|
|
138
168
|
if (this.userConfig.connectionString) {
|
|
139
169
|
this.mcpServer.resource(
|
|
140
170
|
"connection-string",
|
|
@@ -155,4 +185,29 @@ export class Server {
|
|
|
155
185
|
);
|
|
156
186
|
}
|
|
157
187
|
}
|
|
188
|
+
|
|
189
|
+
private async validateConfig(): Promise<void> {
|
|
190
|
+
const isAtlasConfigured = this.userConfig.apiClientId && this.userConfig.apiClientSecret;
|
|
191
|
+
const isMongoDbConfigured = this.userConfig.connectionString;
|
|
192
|
+
if (!isAtlasConfigured && !isMongoDbConfigured) {
|
|
193
|
+
console.error(
|
|
194
|
+
"Either Atlas Client Id or a MongoDB connection string must be configured - you can provide them as environment variables or as startup arguments. \n" +
|
|
195
|
+
"Provide the Atlas credentials as `MDB_MCP_API_CLIENT_ID` and `MDB_MCP_API_CLIENT_SECRET` environment variables or as `--apiClientId` and `--apiClientSecret` startup arguments. \n" +
|
|
196
|
+
"Provide the MongoDB connection string as `MDB_MCP_CONNECTION_STRING` environment variable or as `--connectionString` startup argument."
|
|
197
|
+
);
|
|
198
|
+
throw new Error("Either Atlas Client Id or a MongoDB connection string must be configured");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this.userConfig.connectionString) {
|
|
202
|
+
try {
|
|
203
|
+
await this.session.connectToMongoDB(this.userConfig.connectionString, this.userConfig.connectOptions);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(
|
|
206
|
+
"Failed to connect to MongoDB instance using the connection string from the config: ",
|
|
207
|
+
error
|
|
208
|
+
);
|
|
209
|
+
throw new Error("Failed to connect to MongoDB instance using the connection string from the config");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
158
213
|
}
|
package/src/session.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
|
|
2
2
|
import { ApiClient, ApiClientCredentials } from "./common/atlas/apiClient.js";
|
|
3
3
|
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import logger, { LogId } from "./logger.js";
|
|
5
|
+
import EventEmitter from "events";
|
|
6
|
+
import { ConnectOptions } from "./config.js";
|
|
4
7
|
|
|
5
8
|
export interface SessionOptions {
|
|
6
|
-
apiBaseUrl
|
|
9
|
+
apiBaseUrl: string;
|
|
7
10
|
apiClientId?: string;
|
|
8
11
|
apiClientSecret?: string;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export class Session {
|
|
14
|
+
export class Session extends EventEmitter<{
|
|
15
|
+
close: [];
|
|
16
|
+
disconnect: [];
|
|
17
|
+
}> {
|
|
12
18
|
sessionId?: string;
|
|
13
19
|
serviceProvider?: NodeDriverServiceProvider;
|
|
14
20
|
apiClient: ApiClient;
|
|
@@ -16,8 +22,16 @@ export class Session {
|
|
|
16
22
|
name: string;
|
|
17
23
|
version: string;
|
|
18
24
|
};
|
|
25
|
+
connectedAtlasCluster?: {
|
|
26
|
+
username: string;
|
|
27
|
+
projectId: string;
|
|
28
|
+
clusterName: string;
|
|
29
|
+
expiryDate: Date;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
constructor({ apiBaseUrl, apiClientId, apiClientSecret }: SessionOptions) {
|
|
33
|
+
super();
|
|
19
34
|
|
|
20
|
-
constructor({ apiBaseUrl, apiClientId, apiClientSecret }: SessionOptions = {}) {
|
|
21
35
|
const credentials: ApiClientCredentials | undefined =
|
|
22
36
|
apiClientId && apiClientSecret
|
|
23
37
|
? {
|
|
@@ -41,14 +55,63 @@ export class Session {
|
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
async
|
|
58
|
+
async disconnect(): Promise<void> {
|
|
45
59
|
if (this.serviceProvider) {
|
|
46
60
|
try {
|
|
47
61
|
await this.serviceProvider.close(true);
|
|
48
|
-
} catch (
|
|
49
|
-
|
|
62
|
+
} catch (err: unknown) {
|
|
63
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
64
|
+
logger.error(LogId.mongodbDisconnectFailure, "Error closing service provider:", error.message);
|
|
50
65
|
}
|
|
51
66
|
this.serviceProvider = undefined;
|
|
52
67
|
}
|
|
68
|
+
if (!this.connectedAtlasCluster) {
|
|
69
|
+
this.emit("disconnect");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await this.apiClient.deleteDatabaseUser({
|
|
74
|
+
params: {
|
|
75
|
+
path: {
|
|
76
|
+
groupId: this.connectedAtlasCluster.projectId,
|
|
77
|
+
username: this.connectedAtlasCluster.username,
|
|
78
|
+
databaseName: "admin",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
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
|
+
this.connectedAtlasCluster = undefined;
|
|
92
|
+
|
|
93
|
+
this.emit("disconnect");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async close(): Promise<void> {
|
|
97
|
+
await this.disconnect();
|
|
98
|
+
this.emit("close");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async connectToMongoDB(connectionString: string, connectOptions: ConnectOptions): Promise<void> {
|
|
102
|
+
const provider = await NodeDriverServiceProvider.connect(connectionString, {
|
|
103
|
+
productDocsLink: "https://docs.mongodb.com/todo-mcp",
|
|
104
|
+
productName: "MongoDB MCP",
|
|
105
|
+
readConcern: {
|
|
106
|
+
level: connectOptions.readConcern,
|
|
107
|
+
},
|
|
108
|
+
readPreference: connectOptions.readPreference,
|
|
109
|
+
writeConcern: {
|
|
110
|
+
w: connectOptions.writeConcern,
|
|
111
|
+
},
|
|
112
|
+
timeoutMS: connectOptions.timeoutMS,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.serviceProvider = provider;
|
|
53
116
|
}
|
|
54
117
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { getMachineIdSync } from "native-machine-id";
|
|
2
2
|
import { packageInfo } from "../packageInfo.js";
|
|
3
|
-
|
|
3
|
+
import { type CommonStaticProperties } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Machine-specific metadata formatted for telemetry
|
|
6
6
|
*/
|
|
7
|
-
export const MACHINE_METADATA = {
|
|
7
|
+
export const MACHINE_METADATA: CommonStaticProperties = {
|
|
8
8
|
device_id: getMachineIdSync(),
|
|
9
9
|
mcp_server_version: packageInfo.version,
|
|
10
10
|
mcp_server_name: packageInfo.mcpServerName,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Session } from "../session.js";
|
|
2
|
-
import { BaseEvent } from "./types.js";
|
|
2
|
+
import { BaseEvent, CommonProperties } from "./types.js";
|
|
3
3
|
import { config } from "../config.js";
|
|
4
|
-
import logger from "../logger.js";
|
|
5
|
-
import { mongoLogId } from "mongodb-log-writer";
|
|
4
|
+
import logger, { LogId } from "../logger.js";
|
|
6
5
|
import { ApiClient } from "../common/atlas/apiClient.js";
|
|
7
6
|
import { MACHINE_METADATA } from "./constants.js";
|
|
8
7
|
import { EventCache } from "./eventCache.js";
|
|
@@ -12,19 +11,6 @@ type EventResult = {
|
|
|
12
11
|
error?: Error;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
type CommonProperties = {
|
|
16
|
-
device_id?: string;
|
|
17
|
-
mcp_server_version: string;
|
|
18
|
-
mcp_server_name: string;
|
|
19
|
-
mcp_client_version?: string;
|
|
20
|
-
mcp_client_name?: string;
|
|
21
|
-
platform: string;
|
|
22
|
-
arch: string;
|
|
23
|
-
os_type: string;
|
|
24
|
-
os_version?: string;
|
|
25
|
-
session_id?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
14
|
export class Telemetry {
|
|
29
15
|
private readonly commonProperties: CommonProperties;
|
|
30
16
|
|
|
@@ -74,7 +60,7 @@ export class Telemetry {
|
|
|
74
60
|
|
|
75
61
|
await this.emit(events);
|
|
76
62
|
} catch {
|
|
77
|
-
logger.debug(
|
|
63
|
+
logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
|
|
78
64
|
}
|
|
79
65
|
}
|
|
80
66
|
|
|
@@ -88,6 +74,8 @@ export class Telemetry {
|
|
|
88
74
|
mcp_client_version: this.session.agentRunner?.version,
|
|
89
75
|
mcp_client_name: this.session.agentRunner?.name,
|
|
90
76
|
session_id: this.session.sessionId,
|
|
77
|
+
config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
|
|
78
|
+
config_connection_string: config.connectionString ? "true" : "false",
|
|
91
79
|
};
|
|
92
80
|
}
|
|
93
81
|
|
|
@@ -100,7 +88,7 @@ export class Telemetry {
|
|
|
100
88
|
const allEvents = [...cachedEvents, ...events];
|
|
101
89
|
|
|
102
90
|
logger.debug(
|
|
103
|
-
|
|
91
|
+
LogId.telemetryEmitStart,
|
|
104
92
|
"telemetry",
|
|
105
93
|
`Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
|
|
106
94
|
);
|
|
@@ -108,12 +96,12 @@ export class Telemetry {
|
|
|
108
96
|
const result = await this.sendEvents(this.session.apiClient, allEvents);
|
|
109
97
|
if (result.success) {
|
|
110
98
|
this.eventCache.clearEvents();
|
|
111
|
-
logger.debug(
|
|
99
|
+
logger.debug(LogId.telemetryEmitSuccess, "telemetry", `Sent ${allEvents.length} events successfully`);
|
|
112
100
|
return;
|
|
113
101
|
}
|
|
114
102
|
|
|
115
|
-
logger.
|
|
116
|
-
|
|
103
|
+
logger.debug(
|
|
104
|
+
LogId.telemetryEmitFailure,
|
|
117
105
|
"telemetry",
|
|
118
106
|
`Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`
|
|
119
107
|
);
|
package/src/telemetry/types.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type TelemetryResult = "success" | "failure";
|
|
5
5
|
export type ServerCommand = "start" | "stop";
|
|
6
|
+
export type TelemetryBoolSet = "true" | "false";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Base interface for all events
|
|
@@ -14,21 +15,11 @@ export interface Event {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface BaseEvent extends Event {
|
|
17
|
-
properties: {
|
|
18
|
-
device_id?: string;
|
|
19
|
-
mcp_server_version: string;
|
|
20
|
-
mcp_server_name: string;
|
|
21
|
-
mcp_client_version?: string;
|
|
22
|
-
mcp_client_name?: string;
|
|
23
|
-
platform: string;
|
|
24
|
-
arch: string;
|
|
25
|
-
os_type: string;
|
|
18
|
+
properties: CommonProperties & {
|
|
26
19
|
component: string;
|
|
27
20
|
duration_ms: number;
|
|
28
21
|
result: TelemetryResult;
|
|
29
22
|
category: string;
|
|
30
|
-
os_version?: string;
|
|
31
|
-
session_id?: string;
|
|
32
23
|
} & Event["properties"];
|
|
33
24
|
}
|
|
34
25
|
|
|
@@ -56,5 +47,31 @@ export interface ServerEvent extends BaseEvent {
|
|
|
56
47
|
reason?: string;
|
|
57
48
|
startup_time_ms?: number;
|
|
58
49
|
runtime_duration_ms?: number;
|
|
50
|
+
read_only_mode?: boolean;
|
|
51
|
+
disabled_tools?: string[];
|
|
59
52
|
} & BaseEvent["properties"];
|
|
60
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Interface for static properties, they can be fetched once and reused.
|
|
57
|
+
*/
|
|
58
|
+
export type CommonStaticProperties = {
|
|
59
|
+
device_id?: string;
|
|
60
|
+
mcp_server_version: string;
|
|
61
|
+
mcp_server_name: string;
|
|
62
|
+
platform: string;
|
|
63
|
+
arch: string;
|
|
64
|
+
os_type: string;
|
|
65
|
+
os_version?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Common properties for all events that might change.
|
|
70
|
+
*/
|
|
71
|
+
export type CommonProperties = {
|
|
72
|
+
mcp_client_version?: string;
|
|
73
|
+
mcp_client_name?: string;
|
|
74
|
+
config_atlas_auth?: TelemetryBoolSet;
|
|
75
|
+
config_connection_string?: TelemetryBoolSet;
|
|
76
|
+
session_id?: string;
|
|
77
|
+
} & CommonStaticProperties;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
5
|
|
|
6
6
|
const DEFAULT_COMMENT = "Added by Atlas MCP";
|
|
7
7
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { CloudDatabaseUser, DatabaseUserRole } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { CloudDatabaseUser, DatabaseUserRole } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class CreateDBUserTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-create-db-user";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { ClusterDescription20240805 } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { ClusterDescription20240805 } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class CreateFreeClusterTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-create-free-cluster";
|
|
@@ -47,7 +47,10 @@ export class CreateFreeClusterTool extends AtlasToolBase {
|
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
return {
|
|
50
|
-
content: [
|
|
50
|
+
content: [
|
|
51
|
+
{ type: "text", text: `Cluster "${name}" has been created in region "${region}".` },
|
|
52
|
+
{ type: "text", text: `Double check your access lists to enable your current IP.` },
|
|
53
|
+
],
|
|
51
54
|
};
|
|
52
55
|
}
|
|
53
56
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { Group } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { Group } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class CreateProjectTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-create-project";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
|
|
8
|
+
const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours
|
|
9
|
+
|
|
10
|
+
const randomBytesAsync = promisify(randomBytes);
|
|
11
|
+
|
|
12
|
+
async function generateSecurePassword(): Promise<string> {
|
|
13
|
+
const buf = await randomBytesAsync(16);
|
|
14
|
+
const pass = buf.toString("base64url");
|
|
15
|
+
return pass;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ConnectClusterTool extends AtlasToolBase {
|
|
19
|
+
protected name = "atlas-connect-cluster";
|
|
20
|
+
protected description = "Connect to MongoDB Atlas cluster";
|
|
21
|
+
protected operationType: OperationType = "metadata";
|
|
22
|
+
protected argsShape = {
|
|
23
|
+
projectId: z.string().describe("Atlas project ID"),
|
|
24
|
+
clusterName: z.string().describe("Atlas cluster name"),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
protected async execute({ projectId, clusterName }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
28
|
+
await this.session.disconnect();
|
|
29
|
+
|
|
30
|
+
const cluster = await this.session.apiClient.getCluster({
|
|
31
|
+
params: {
|
|
32
|
+
path: {
|
|
33
|
+
groupId: projectId,
|
|
34
|
+
clusterName,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!cluster) {
|
|
40
|
+
throw new Error("Cluster not found");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const baseConnectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
|
|
44
|
+
|
|
45
|
+
if (!baseConnectionString) {
|
|
46
|
+
throw new Error("Connection string not available");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const username = `mcpUser${Math.floor(Math.random() * 100000)}`;
|
|
50
|
+
const password = await generateSecurePassword();
|
|
51
|
+
|
|
52
|
+
const expiryDate = new Date(Date.now() + EXPIRY_MS);
|
|
53
|
+
|
|
54
|
+
const readOnly =
|
|
55
|
+
this.config.readOnly ||
|
|
56
|
+
(this.config.disabledTools?.includes("create") &&
|
|
57
|
+
this.config.disabledTools?.includes("update") &&
|
|
58
|
+
this.config.disabledTools?.includes("delete") &&
|
|
59
|
+
!this.config.disabledTools?.includes("read") &&
|
|
60
|
+
!this.config.disabledTools?.includes("metadata"));
|
|
61
|
+
|
|
62
|
+
const roleName = readOnly ? "readAnyDatabase" : "readWriteAnyDatabase";
|
|
63
|
+
|
|
64
|
+
await this.session.apiClient.createDatabaseUser({
|
|
65
|
+
params: {
|
|
66
|
+
path: {
|
|
67
|
+
groupId: projectId,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
body: {
|
|
71
|
+
databaseName: "admin",
|
|
72
|
+
groupId: projectId,
|
|
73
|
+
roles: [
|
|
74
|
+
{
|
|
75
|
+
roleName,
|
|
76
|
+
databaseName: "admin",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
scopes: [{ type: "CLUSTER", name: clusterName }],
|
|
80
|
+
username,
|
|
81
|
+
password,
|
|
82
|
+
awsIAMType: "NONE",
|
|
83
|
+
ldapAuthType: "NONE",
|
|
84
|
+
oidcAuthType: "NONE",
|
|
85
|
+
x509Type: "NONE",
|
|
86
|
+
deleteAfterDate: expiryDate.toISOString(),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.session.connectedAtlasCluster = {
|
|
91
|
+
username,
|
|
92
|
+
projectId,
|
|
93
|
+
clusterName,
|
|
94
|
+
expiryDate,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const cn = new URL(baseConnectionString);
|
|
98
|
+
cn.username = username;
|
|
99
|
+
cn.password = password;
|
|
100
|
+
cn.searchParams.set("authSource", "admin");
|
|
101
|
+
const connectionString = cn.toString();
|
|
102
|
+
|
|
103
|
+
await this.session.connectToMongoDB(connectionString, this.config.connectOptions);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `Connected to cluster "${clusterName}"`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
5
|
|
|
6
6
|
export class InspectAccessListTool extends AtlasToolBase {
|
|
7
7
|
protected name = "atlas-inspect-access-list";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { ClusterDescription20240805 } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { ClusterDescription20240805 } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class InspectClusterTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-inspect-cluster";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { PaginatedClusterDescription20240805, PaginatedOrgGroupView, Group } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { PaginatedClusterDescription20240805, PaginatedOrgGroupView, Group } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class ListClustersTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-list-clusters";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { AtlasToolBase } from "
|
|
4
|
-
import { ToolArgs, OperationType } from "
|
|
5
|
-
import { DatabaseUserRole, UserScope } from "
|
|
3
|
+
import { AtlasToolBase } from "../atlasTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { DatabaseUserRole, UserScope } from "../../../common/atlas/openapi.js";
|
|
6
6
|
|
|
7
7
|
export class ListDBUsersTool extends AtlasToolBase {
|
|
8
8
|
protected name = "atlas-list-db-users";
|