mongodb-mcp-server 0.0.4

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 (164) hide show
  1. package/.github/workflows/code_health.yaml +65 -0
  2. package/.github/workflows/prepare_release.yaml +45 -0
  3. package/.github/workflows/publish.yaml +70 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc.json +37 -0
  6. package/.vscode/launch.json +17 -0
  7. package/CONTRIBUTING.md +185 -0
  8. package/LICENSE +202 -0
  9. package/README.md +234 -0
  10. package/dist/common/atlas/apiClient.js +147 -0
  11. package/dist/common/atlas/apiClient.js.map +1 -0
  12. package/dist/common/atlas/apiClientError.js +17 -0
  13. package/dist/common/atlas/apiClientError.js.map +1 -0
  14. package/dist/config.js +85 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/errors.js +12 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.js +26 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/logger.js +97 -0
  21. package/dist/logger.js.map +1 -0
  22. package/dist/server.js +45 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/session.js +30 -0
  25. package/dist/session.js.map +1 -0
  26. package/dist/tools/atlas/atlasTool.js +9 -0
  27. package/dist/tools/atlas/atlasTool.js.map +1 -0
  28. package/dist/tools/atlas/createAccessList.js +64 -0
  29. package/dist/tools/atlas/createAccessList.js.map +1 -0
  30. package/dist/tools/atlas/createDBUser.js +58 -0
  31. package/dist/tools/atlas/createDBUser.js.map +1 -0
  32. package/dist/tools/atlas/createFreeCluster.js +51 -0
  33. package/dist/tools/atlas/createFreeCluster.js.map +1 -0
  34. package/dist/tools/atlas/createProject.js +53 -0
  35. package/dist/tools/atlas/createProject.js.map +1 -0
  36. package/dist/tools/atlas/inspectAccessList.js +41 -0
  37. package/dist/tools/atlas/inspectAccessList.js.map +1 -0
  38. package/dist/tools/atlas/inspectCluster.js +42 -0
  39. package/dist/tools/atlas/inspectCluster.js.map +1 -0
  40. package/dist/tools/atlas/listClusters.js +97 -0
  41. package/dist/tools/atlas/listClusters.js.map +1 -0
  42. package/dist/tools/atlas/listDBUsers.js +52 -0
  43. package/dist/tools/atlas/listDBUsers.js.map +1 -0
  44. package/dist/tools/atlas/listOrgs.js +30 -0
  45. package/dist/tools/atlas/listOrgs.js.map +1 -0
  46. package/dist/tools/atlas/listProjects.js +42 -0
  47. package/dist/tools/atlas/listProjects.js.map +1 -0
  48. package/dist/tools/atlas/tools.js +23 -0
  49. package/dist/tools/atlas/tools.js.map +1 -0
  50. package/dist/tools/mongodb/create/createCollection.js +23 -0
  51. package/dist/tools/mongodb/create/createCollection.js.map +1 -0
  52. package/dist/tools/mongodb/create/createIndex.js +33 -0
  53. package/dist/tools/mongodb/create/createIndex.js.map +1 -0
  54. package/dist/tools/mongodb/create/insertMany.js +33 -0
  55. package/dist/tools/mongodb/create/insertMany.js.map +1 -0
  56. package/dist/tools/mongodb/delete/deleteMany.js +31 -0
  57. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -0
  58. package/dist/tools/mongodb/delete/dropCollection.js +25 -0
  59. package/dist/tools/mongodb/delete/dropCollection.js.map +1 -0
  60. package/dist/tools/mongodb/delete/dropDatabase.js +25 -0
  61. package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -0
  62. package/dist/tools/mongodb/metadata/collectionSchema.js +38 -0
  63. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -0
  64. package/dist/tools/mongodb/metadata/collectionStorageSize.js +28 -0
  65. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -0
  66. package/dist/tools/mongodb/metadata/connect.js +86 -0
  67. package/dist/tools/mongodb/metadata/connect.js.map +1 -0
  68. package/dist/tools/mongodb/metadata/dbStats.js +28 -0
  69. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -0
  70. package/dist/tools/mongodb/metadata/explain.js +77 -0
  71. package/dist/tools/mongodb/metadata/explain.js.map +1 -0
  72. package/dist/tools/mongodb/metadata/listCollections.js +35 -0
  73. package/dist/tools/mongodb/metadata/listCollections.js.map +1 -0
  74. package/dist/tools/mongodb/metadata/listDatabases.js +23 -0
  75. package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -0
  76. package/dist/tools/mongodb/mongodbTool.js +58 -0
  77. package/dist/tools/mongodb/mongodbTool.js.map +1 -0
  78. package/dist/tools/mongodb/read/aggregate.js +38 -0
  79. package/dist/tools/mongodb/read/aggregate.js.map +1 -0
  80. package/dist/tools/mongodb/read/collectionIndexes.js +23 -0
  81. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -0
  82. package/dist/tools/mongodb/read/count.js +34 -0
  83. package/dist/tools/mongodb/read/count.js.map +1 -0
  84. package/dist/tools/mongodb/read/find.js +51 -0
  85. package/dist/tools/mongodb/read/find.js.map +1 -0
  86. package/dist/tools/mongodb/tools.js +41 -0
  87. package/dist/tools/mongodb/tools.js.map +1 -0
  88. package/dist/tools/mongodb/update/renameCollection.js +31 -0
  89. package/dist/tools/mongodb/update/renameCollection.js.map +1 -0
  90. package/dist/tools/mongodb/update/updateMany.js +56 -0
  91. package/dist/tools/mongodb/update/updateMany.js.map +1 -0
  92. package/dist/tools/tool.js +56 -0
  93. package/dist/tools/tool.js.map +1 -0
  94. package/eslint.config.js +35 -0
  95. package/jest.config.js +22 -0
  96. package/package.json +76 -0
  97. package/scripts/apply.ts +129 -0
  98. package/scripts/filter.ts +67 -0
  99. package/scripts/generate.sh +11 -0
  100. package/src/common/atlas/apiClient.ts +202 -0
  101. package/src/common/atlas/apiClientError.ts +21 -0
  102. package/src/common/atlas/openapi.d.ts +5849 -0
  103. package/src/config.ts +124 -0
  104. package/src/errors.ts +13 -0
  105. package/src/index.ts +30 -0
  106. package/src/logger.ts +117 -0
  107. package/src/server.ts +64 -0
  108. package/src/session.ts +37 -0
  109. package/src/tools/atlas/atlasTool.ts +10 -0
  110. package/src/tools/atlas/createAccessList.ts +78 -0
  111. package/src/tools/atlas/createDBUser.ts +70 -0
  112. package/src/tools/atlas/createFreeCluster.ts +55 -0
  113. package/src/tools/atlas/createProject.ts +63 -0
  114. package/src/tools/atlas/inspectAccessList.ts +44 -0
  115. package/src/tools/atlas/inspectCluster.ts +47 -0
  116. package/src/tools/atlas/listClusters.ts +104 -0
  117. package/src/tools/atlas/listDBUsers.ts +62 -0
  118. package/src/tools/atlas/listOrgs.ts +34 -0
  119. package/src/tools/atlas/listProjects.ts +46 -0
  120. package/src/tools/atlas/tools.ts +23 -0
  121. package/src/tools/mongodb/create/createCollection.ts +26 -0
  122. package/src/tools/mongodb/create/createIndex.ts +41 -0
  123. package/src/tools/mongodb/create/insertMany.ts +40 -0
  124. package/src/tools/mongodb/delete/deleteMany.ts +38 -0
  125. package/src/tools/mongodb/delete/dropCollection.ts +27 -0
  126. package/src/tools/mongodb/delete/dropDatabase.ts +26 -0
  127. package/src/tools/mongodb/metadata/collectionSchema.ts +41 -0
  128. package/src/tools/mongodb/metadata/collectionStorageSize.ts +30 -0
  129. package/src/tools/mongodb/metadata/connect.ts +94 -0
  130. package/src/tools/mongodb/metadata/dbStats.ts +30 -0
  131. package/src/tools/mongodb/metadata/explain.ts +90 -0
  132. package/src/tools/mongodb/metadata/listCollections.ts +38 -0
  133. package/src/tools/mongodb/metadata/listDatabases.ts +26 -0
  134. package/src/tools/mongodb/mongodbTool.ts +69 -0
  135. package/src/tools/mongodb/read/aggregate.ts +45 -0
  136. package/src/tools/mongodb/read/collectionIndexes.ts +24 -0
  137. package/src/tools/mongodb/read/count.ts +39 -0
  138. package/src/tools/mongodb/read/find.ts +62 -0
  139. package/src/tools/mongodb/tools.ts +41 -0
  140. package/src/tools/mongodb/update/renameCollection.ts +37 -0
  141. package/src/tools/mongodb/update/updateMany.ts +65 -0
  142. package/src/tools/tool.ts +90 -0
  143. package/src/types/mongodb-redact.d.ts +4 -0
  144. package/tests/integration/helpers.ts +241 -0
  145. package/tests/integration/inMemoryTransport.ts +58 -0
  146. package/tests/integration/server.test.ts +35 -0
  147. package/tests/integration/tools/atlas/accessLists.test.ts +100 -0
  148. package/tests/integration/tools/atlas/atlasHelpers.ts +110 -0
  149. package/tests/integration/tools/atlas/clusters.test.ts +122 -0
  150. package/tests/integration/tools/atlas/dbUsers.test.ts +80 -0
  151. package/tests/integration/tools/atlas/orgs.test.ts +24 -0
  152. package/tests/integration/tools/atlas/projects.test.ts +80 -0
  153. package/tests/integration/tools/mongodb/create/createCollection.test.ts +138 -0
  154. package/tests/integration/tools/mongodb/create/createIndex.test.ts +249 -0
  155. package/tests/integration/tools/mongodb/create/insertMany.test.ts +141 -0
  156. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +191 -0
  157. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +118 -0
  158. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +114 -0
  159. package/tests/integration/tools/mongodb/metadata/connect.test.ts +137 -0
  160. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +104 -0
  161. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +67 -0
  162. package/tests/integration/tools/mongodb/read/count.test.ts +138 -0
  163. package/tsconfig.jest.json +10 -0
  164. package/tsconfig.json +19 -0
package/src/config.ts ADDED
@@ -0,0 +1,124 @@
1
+ import path from "path";
2
+ import os from "os";
3
+ import argv from "yargs-parser";
4
+
5
+ import packageJson from "../package.json" with { type: "json" };
6
+ import { ReadConcernLevel, ReadPreferenceMode, W } from "mongodb";
7
+
8
+ // If we decide to support non-string config options, we'll need to extend the mechanism for parsing
9
+ // env variables.
10
+ interface UserConfig {
11
+ apiBaseUrl?: string;
12
+ apiClientId?: string;
13
+ apiClientSecret?: string;
14
+ logPath: string;
15
+ connectionString?: string;
16
+ connectOptions: {
17
+ readConcern: ReadConcernLevel;
18
+ readPreference: ReadPreferenceMode;
19
+ writeConcern: W;
20
+ timeoutMS: number;
21
+ };
22
+ disabledTools: Array<string>;
23
+ }
24
+
25
+ const defaults: UserConfig = {
26
+ logPath: getLogPath(),
27
+ connectOptions: {
28
+ readConcern: "local",
29
+ readPreference: "secondaryPreferred",
30
+ writeConcern: "majority",
31
+ timeoutMS: 30_000,
32
+ },
33
+ disabledTools: [],
34
+ };
35
+
36
+ const mergedUserConfig = {
37
+ ...defaults,
38
+ ...getEnvConfig(),
39
+ ...getCliConfig(),
40
+ };
41
+
42
+ const config = {
43
+ ...mergedUserConfig,
44
+ version: packageJson.version,
45
+ };
46
+
47
+ export default config;
48
+
49
+ function getLogPath(): string {
50
+ const localDataPath =
51
+ process.platform === "win32"
52
+ ? path.join(process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir(), "mongodb")
53
+ : path.join(os.homedir(), ".mongodb");
54
+
55
+ const logPath = path.join(localDataPath, "mongodb-mcp", ".app-logs");
56
+
57
+ return logPath;
58
+ }
59
+
60
+ // Gets the config supplied by the user as environment variables. The variable names
61
+ // are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted
62
+ // to SNAKE_UPPER_CASE.
63
+ function getEnvConfig(): Partial<UserConfig> {
64
+ function setValue(obj: Record<string, unknown>, path: string[], value: string): void {
65
+ const currentField = path.shift();
66
+ if (!currentField) {
67
+ return;
68
+ }
69
+ if (path.length === 0) {
70
+ const numberValue = Number(value);
71
+ if (!isNaN(numberValue)) {
72
+ obj[currentField] = numberValue;
73
+ return;
74
+ }
75
+
76
+ const booleanValue = value.toLocaleLowerCase();
77
+ if (booleanValue === "true" || booleanValue === "false") {
78
+ obj[currentField] = booleanValue === "true";
79
+ return;
80
+ }
81
+
82
+ // Try to parse an array of values
83
+ if (value.indexOf(",") !== -1) {
84
+ obj[currentField] = value.split(",").map((v) => v.trim());
85
+ return;
86
+ }
87
+
88
+ obj[currentField] = value;
89
+ return;
90
+ }
91
+
92
+ if (!obj[currentField]) {
93
+ obj[currentField] = {};
94
+ }
95
+
96
+ setValue(obj[currentField] as Record<string, unknown>, path, value);
97
+ }
98
+
99
+ const result: Record<string, unknown> = {};
100
+ const mcpVariables = Object.entries(process.env).filter(
101
+ ([key, value]) => value !== undefined && key.startsWith("MDB_MCP_")
102
+ ) as [string, string][];
103
+ for (const [key, value] of mcpVariables) {
104
+ const fieldPath = key
105
+ .replace("MDB_MCP_", "")
106
+ .split(".")
107
+ .map((part) => SNAKE_CASE_toCamelCase(part));
108
+
109
+ setValue(result, fieldPath, value);
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ function SNAKE_CASE_toCamelCase(str: string): string {
116
+ return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace("_", ""));
117
+ }
118
+
119
+ // Reads the cli args and parses them into a UserConfig object.
120
+ function getCliConfig() {
121
+ return argv(process.argv.slice(2), {
122
+ array: ["disabledTools"],
123
+ }) as unknown as Partial<UserConfig>;
124
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,13 @@
1
+ export enum ErrorCodes {
2
+ NotConnectedToMongoDB = 1_000_000,
3
+ InvalidParams = 1_000_001,
4
+ }
5
+
6
+ export class MongoDBError extends Error {
7
+ constructor(
8
+ public code: ErrorCodes,
9
+ message: string
10
+ ) {
11
+ super(message);
12
+ }
13
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import logger from "./logger.js";
5
+ import { mongoLogId } from "mongodb-log-writer";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import config from "./config.js";
8
+ import { Session } from "./session.js";
9
+ import { Server } from "./server.js";
10
+
11
+ try {
12
+ const session = new Session();
13
+ const mcpServer = new McpServer({
14
+ name: "MongoDB Atlas",
15
+ version: config.version,
16
+ });
17
+
18
+ const server = new Server({
19
+ mcpServer,
20
+ session,
21
+ });
22
+
23
+ const transport = new StdioServerTransport();
24
+
25
+ await server.connect(transport);
26
+ } catch (error: unknown) {
27
+ logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error as string}`);
28
+
29
+ process.exit(1);
30
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,117 @@
1
+ import fs from "fs/promises";
2
+ import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
3
+ import config from "./config.js";
4
+ import redact from "mongodb-redact";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ export type LogLevel = LoggingMessageNotification["params"]["level"];
9
+
10
+ abstract class LoggerBase {
11
+ abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
12
+ info(id: MongoLogId, context: string, message: string): void {
13
+ this.log("info", id, context, message);
14
+ }
15
+
16
+ error(id: MongoLogId, context: string, message: string): void {
17
+ this.log("error", id, context, message);
18
+ }
19
+ debug(id: MongoLogId, context: string, message: string): void {
20
+ this.log("debug", id, context, message);
21
+ }
22
+
23
+ notice(id: MongoLogId, context: string, message: string): void {
24
+ this.log("notice", id, context, message);
25
+ }
26
+
27
+ warning(id: MongoLogId, context: string, message: string): void {
28
+ this.log("warning", id, context, message);
29
+ }
30
+
31
+ critical(id: MongoLogId, context: string, message: string): void {
32
+ this.log("critical", id, context, message);
33
+ }
34
+
35
+ alert(id: MongoLogId, context: string, message: string): void {
36
+ this.log("alert", id, context, message);
37
+ }
38
+
39
+ emergency(id: MongoLogId, context: string, message: string): void {
40
+ this.log("emergency", id, context, message);
41
+ }
42
+ }
43
+
44
+ class ConsoleLogger extends LoggerBase {
45
+ log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
46
+ message = redact(message);
47
+ console.error(`[${level.toUpperCase()}] ${id.__value} - ${context}: ${message}`);
48
+ }
49
+ }
50
+
51
+ class Logger extends LoggerBase {
52
+ constructor(
53
+ private logWriter: MongoLogWriter,
54
+ private server: McpServer
55
+ ) {
56
+ super();
57
+ }
58
+
59
+ log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
60
+ message = redact(message);
61
+ const mongoDBLevel = this.mapToMongoDBLogLevel(level);
62
+ this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message);
63
+ void this.server.server.sendLoggingMessage({
64
+ level,
65
+ data: `[${context}]: ${message}`,
66
+ });
67
+ }
68
+
69
+ private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
70
+ switch (level) {
71
+ case "info":
72
+ return "info";
73
+ case "warning":
74
+ return "warn";
75
+ case "error":
76
+ return "error";
77
+ case "notice":
78
+ case "debug":
79
+ return "debug";
80
+ case "critical":
81
+ case "alert":
82
+ case "emergency":
83
+ return "fatal";
84
+ default:
85
+ return "info";
86
+ }
87
+ }
88
+ }
89
+
90
+ class ProxyingLogger extends LoggerBase {
91
+ private internalLogger: LoggerBase = new ConsoleLogger();
92
+
93
+ log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
94
+ this.internalLogger.log(level, id, context, message);
95
+ }
96
+ }
97
+
98
+ const logger = new ProxyingLogger();
99
+ export default logger;
100
+
101
+ export async function initializeLogger(server: McpServer): Promise<void> {
102
+ await fs.mkdir(config.logPath, { recursive: true });
103
+
104
+ const manager = new MongoLogManager({
105
+ directory: config.logPath,
106
+ retentionDays: 30,
107
+ onwarn: console.warn,
108
+ onerror: console.error,
109
+ gzip: false,
110
+ retentionGB: 1,
111
+ });
112
+
113
+ await manager.cleanupOldLogFiles();
114
+
115
+ const logWriter = await manager.createLogWriter();
116
+ logger["internalLogger"] = new Logger(logWriter, server);
117
+ }
package/src/server.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { Session } from "./session.js";
3
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
+ import { AtlasTools } from "./tools/atlas/tools.js";
5
+ import { MongoDbTools } from "./tools/mongodb/tools.js";
6
+ import logger, { initializeLogger } from "./logger.js";
7
+ import { mongoLogId } from "mongodb-log-writer";
8
+ import config from "./config.js";
9
+
10
+ export class Server {
11
+ public readonly session: Session;
12
+ private readonly mcpServer: McpServer;
13
+
14
+ constructor({ mcpServer, session }: { mcpServer: McpServer; session: Session }) {
15
+ this.mcpServer = mcpServer;
16
+ this.session = session;
17
+ }
18
+
19
+ async connect(transport: Transport) {
20
+ this.mcpServer.server.registerCapabilities({ logging: {} });
21
+
22
+ this.registerTools();
23
+ this.registerResources();
24
+
25
+ await initializeLogger(this.mcpServer);
26
+
27
+ await this.mcpServer.connect(transport);
28
+
29
+ logger.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`);
30
+ }
31
+
32
+ async close(): Promise<void> {
33
+ await this.session.close();
34
+ await this.mcpServer.close();
35
+ }
36
+
37
+ private registerTools() {
38
+ for (const tool of [...AtlasTools, ...MongoDbTools]) {
39
+ new tool(this.session).register(this.mcpServer);
40
+ }
41
+ }
42
+
43
+ private registerResources() {
44
+ if (config.connectionString) {
45
+ this.mcpServer.resource(
46
+ "connection-string",
47
+ "config://connection-string",
48
+ {
49
+ description: "Preconfigured connection string that will be used as a default in the `connect` tool",
50
+ },
51
+ (uri) => {
52
+ return {
53
+ contents: [
54
+ {
55
+ text: `Preconfigured connection string: ${config.connectionString}`,
56
+ uri: uri.href,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ );
62
+ }
63
+ }
64
+ }
package/src/session.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
2
+ import { ApiClient } from "./common/atlas/apiClient.js";
3
+ import config from "./config.js";
4
+
5
+ export class Session {
6
+ serviceProvider?: NodeDriverServiceProvider;
7
+ apiClient?: ApiClient;
8
+
9
+ ensureAuthenticated(): asserts this is { apiClient: ApiClient } {
10
+ if (!this.apiClient) {
11
+ if (!config.apiClientId || !config.apiClientSecret) {
12
+ throw new Error(
13
+ "Not authenticated make sure to configure MCP server with MDB_MCP_API_CLIENT_ID and MDB_MCP_API_CLIENT_SECRET environment variables."
14
+ );
15
+ }
16
+
17
+ this.apiClient = new ApiClient({
18
+ baseUrl: config.apiBaseUrl,
19
+ credentials: {
20
+ clientId: config.apiClientId,
21
+ clientSecret: config.apiClientSecret,
22
+ },
23
+ });
24
+ }
25
+ }
26
+
27
+ async close(): Promise<void> {
28
+ if (this.serviceProvider) {
29
+ try {
30
+ await this.serviceProvider.close(true);
31
+ } catch (error) {
32
+ console.error("Error closing service provider:", error);
33
+ }
34
+ this.serviceProvider = undefined;
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,10 @@
1
+ import { ToolBase, ToolCategory } from "../tool.js";
2
+ import { Session } from "../../session.js";
3
+
4
+ export abstract class AtlasToolBase extends ToolBase {
5
+ constructor(protected readonly session: Session) {
6
+ super(session);
7
+ }
8
+
9
+ protected category: ToolCategory = "atlas";
10
+ }
@@ -0,0 +1,78 @@
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
+
6
+ const DEFAULT_COMMENT = "Added by Atlas MCP";
7
+
8
+ export class CreateAccessListTool extends AtlasToolBase {
9
+ protected name = "atlas-create-access-list";
10
+ protected description = "Allow Ip/CIDR ranges to access your MongoDB Atlas clusters.";
11
+ protected operationType: OperationType = "create";
12
+ protected argsShape = {
13
+ projectId: z.string().describe("Atlas project ID"),
14
+ ipAddresses: z
15
+ .array(z.string().ip({ version: "v4" }))
16
+ .describe("IP addresses to allow access from")
17
+ .optional(),
18
+ cidrBlocks: z.array(z.string().cidr()).describe("CIDR blocks to allow access from").optional(),
19
+ currentIpAddress: z.boolean().describe("Add the current IP address").default(false),
20
+ comment: z.string().describe("Comment for the access list entries").default(DEFAULT_COMMENT).optional(),
21
+ };
22
+
23
+ protected async execute({
24
+ projectId,
25
+ ipAddresses,
26
+ cidrBlocks,
27
+ comment,
28
+ currentIpAddress,
29
+ }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
30
+ this.session.ensureAuthenticated();
31
+
32
+ if (!ipAddresses?.length && !cidrBlocks?.length && !currentIpAddress) {
33
+ throw new Error("One of ipAddresses, cidrBlocks, currentIpAddress must be provided.");
34
+ }
35
+
36
+ const ipInputs = (ipAddresses || []).map((ipAddress) => ({
37
+ groupId: projectId,
38
+ ipAddress,
39
+ comment: comment || DEFAULT_COMMENT,
40
+ }));
41
+
42
+ if (currentIpAddress) {
43
+ const currentIp = await this.session.apiClient.getIpInfo();
44
+ const input = {
45
+ groupId: projectId,
46
+ ipAddress: currentIp.currentIpv4Address,
47
+ comment: comment || DEFAULT_COMMENT,
48
+ };
49
+ ipInputs.push(input);
50
+ }
51
+
52
+ const cidrInputs = (cidrBlocks || []).map((cidrBlock) => ({
53
+ groupId: projectId,
54
+ cidrBlock,
55
+ comment: comment || DEFAULT_COMMENT,
56
+ }));
57
+
58
+ const inputs = [...ipInputs, ...cidrInputs];
59
+
60
+ await this.session.apiClient.createProjectIpAccessList({
61
+ params: {
62
+ path: {
63
+ groupId: projectId,
64
+ },
65
+ },
66
+ body: inputs,
67
+ });
68
+
69
+ return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: `IP/CIDR ranges added to access list for project ${projectId}.`,
74
+ },
75
+ ],
76
+ };
77
+ }
78
+ }
@@ -0,0 +1,70 @@
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 { CloudDatabaseUser, DatabaseUserRole } from "../../common/atlas/openapi.js";
6
+
7
+ export class CreateDBUserTool extends AtlasToolBase {
8
+ protected name = "atlas-create-db-user";
9
+ protected description = "Create an MongoDB Atlas database user";
10
+ protected operationType: OperationType = "create";
11
+ protected argsShape = {
12
+ projectId: z.string().describe("Atlas project ID"),
13
+ username: z.string().describe("Username for the new user"),
14
+ password: z.string().describe("Password for the new user"),
15
+ roles: z
16
+ .array(
17
+ z.object({
18
+ roleName: z.string().describe("Role name"),
19
+ databaseName: z.string().describe("Database name").default("admin"),
20
+ collectionName: z.string().describe("Collection name").optional(),
21
+ })
22
+ )
23
+ .describe("Roles for the new user"),
24
+ clusters: z
25
+ .array(z.string())
26
+ .describe("Clusters to assign the user to, leave empty for access to all clusters")
27
+ .optional(),
28
+ };
29
+
30
+ protected async execute({
31
+ projectId,
32
+ username,
33
+ password,
34
+ roles,
35
+ clusters,
36
+ }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
37
+ this.session.ensureAuthenticated();
38
+
39
+ const input = {
40
+ groupId: projectId,
41
+ awsIAMType: "NONE",
42
+ databaseName: "admin",
43
+ ldapAuthType: "NONE",
44
+ oidcAuthType: "NONE",
45
+ x509Type: "NONE",
46
+ username,
47
+ password,
48
+ roles: roles as unknown as DatabaseUserRole[],
49
+ scopes: clusters?.length
50
+ ? clusters.map((cluster) => ({
51
+ type: "CLUSTER",
52
+ name: cluster,
53
+ }))
54
+ : undefined,
55
+ } as CloudDatabaseUser;
56
+
57
+ await this.session.apiClient.createDatabaseUser({
58
+ params: {
59
+ path: {
60
+ groupId: projectId,
61
+ },
62
+ },
63
+ body: input,
64
+ });
65
+
66
+ return {
67
+ content: [{ type: "text", text: `User "${username}" created sucessfully.` }],
68
+ };
69
+ }
70
+ }
@@ -0,0 +1,55 @@
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 { ClusterDescription20240805 } from "../../common/atlas/openapi.js";
6
+
7
+ export class CreateFreeClusterTool extends AtlasToolBase {
8
+ protected name = "atlas-create-free-cluster";
9
+ protected description = "Create a free MongoDB Atlas cluster";
10
+ protected operationType: OperationType = "create";
11
+ protected argsShape = {
12
+ projectId: z.string().describe("Atlas project ID to create the cluster in"),
13
+ name: z.string().describe("Name of the cluster"),
14
+ region: z.string().describe("Region of the cluster").default("US_EAST_1"),
15
+ };
16
+
17
+ protected async execute({ projectId, name, region }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
18
+ this.session.ensureAuthenticated();
19
+
20
+ const input = {
21
+ groupId: projectId,
22
+ name,
23
+ clusterType: "REPLICASET",
24
+ replicationSpecs: [
25
+ {
26
+ zoneName: "Zone 1",
27
+ regionConfigs: [
28
+ {
29
+ providerName: "TENANT",
30
+ backingProviderName: "AWS",
31
+ regionName: region,
32
+ electableSpecs: {
33
+ instanceSize: "M0",
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ ],
39
+ terminationProtectionEnabled: false,
40
+ } as unknown as ClusterDescription20240805;
41
+
42
+ await this.session.apiClient.createCluster({
43
+ params: {
44
+ path: {
45
+ groupId: projectId,
46
+ },
47
+ },
48
+ body: input,
49
+ });
50
+
51
+ return {
52
+ content: [{ type: "text", text: `Cluster "${name}" has been created in region "${region}".` }],
53
+ };
54
+ }
55
+ }
@@ -0,0 +1,63 @@
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 { Group } from "../../common/atlas/openapi.js";
6
+
7
+ export class CreateProjectTool extends AtlasToolBase {
8
+ protected name = "atlas-create-project";
9
+ protected description = "Create a MongoDB Atlas project";
10
+ protected operationType: OperationType = "create";
11
+ protected argsShape = {
12
+ projectName: z.string().optional().describe("Name for the new project"),
13
+ organizationId: z.string().optional().describe("Organization ID for the new project"),
14
+ };
15
+
16
+ protected async execute({ projectName, organizationId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
17
+ this.session.ensureAuthenticated();
18
+ let assumedOrg = false;
19
+
20
+ if (!projectName) {
21
+ projectName = "Atlas Project";
22
+ }
23
+
24
+ if (!organizationId) {
25
+ try {
26
+ const organizations = await this.session.apiClient.listOrganizations();
27
+ if (!organizations?.results?.length) {
28
+ throw new Error(
29
+ "No organizations were found in your MongoDB Atlas account. Please create an organization first."
30
+ );
31
+ }
32
+ organizationId = organizations.results[0].id;
33
+ assumedOrg = true;
34
+ } catch {
35
+ throw new Error(
36
+ "Could not search for organizations in your MongoDB Atlas account, please provide an organization ID or create one first."
37
+ );
38
+ }
39
+ }
40
+
41
+ const input = {
42
+ name: projectName,
43
+ orgId: organizationId,
44
+ } as Group;
45
+
46
+ const group = await this.session.apiClient.createProject({
47
+ body: input,
48
+ });
49
+
50
+ if (!group?.id) {
51
+ throw new Error("Failed to create project");
52
+ }
53
+
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: `Project "${projectName}" created successfully${assumedOrg ? ` (using organizationId ${organizationId}).` : ""}.`,
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ }