mongodb-mcp-server 0.1.3 → 0.2.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 (267) hide show
  1. package/.github/workflows/accuracy-tests.yml +55 -0
  2. package/.github/workflows/check.yml +1 -1
  3. package/.github/workflows/code_health.yaml +1 -1
  4. package/.github/workflows/code_health_fork.yaml +0 -14
  5. package/.github/workflows/dependabot_pr.yaml +26 -0
  6. package/.github/workflows/jira-issue.yml +72 -0
  7. package/.vscode/extensions.json +1 -1
  8. package/.vscode/launch.json +11 -1
  9. package/.vscode/settings.json +1 -11
  10. package/Dockerfile +1 -0
  11. package/README.md +118 -31
  12. package/dist/common/atlas/accessListUtils.js +36 -0
  13. package/dist/common/atlas/accessListUtils.js.map +1 -0
  14. package/dist/common/atlas/apiClient.js +25 -6
  15. package/dist/common/atlas/apiClient.js.map +1 -1
  16. package/dist/common/atlas/cluster.js +1 -1
  17. package/dist/common/atlas/cluster.js.map +1 -1
  18. package/dist/{config.js → common/config.js} +7 -1
  19. package/dist/common/config.js.map +1 -0
  20. package/dist/common/errors.js.map +1 -0
  21. package/dist/{logger.js → common/logger.js} +20 -18
  22. package/dist/common/logger.js.map +1 -0
  23. package/dist/common/managedTimeout.js +20 -0
  24. package/dist/common/managedTimeout.js.map +1 -0
  25. package/dist/common/packageInfo.js.map +1 -0
  26. package/dist/{session.js → common/session.js} +20 -21
  27. package/dist/common/session.js.map +1 -0
  28. package/dist/common/sessionStore.js +73 -0
  29. package/dist/common/sessionStore.js.map +1 -0
  30. package/dist/helpers/container.js +28 -0
  31. package/dist/helpers/container.js.map +1 -0
  32. package/dist/helpers/generatePassword.js.map +1 -0
  33. package/dist/helpers/indexCheck.js +1 -1
  34. package/dist/helpers/indexCheck.js.map +1 -1
  35. package/dist/index.js +30 -37
  36. package/dist/index.js.map +1 -1
  37. package/dist/server.js +43 -6
  38. package/dist/server.js.map +1 -1
  39. package/dist/telemetry/constants.js +1 -1
  40. package/dist/telemetry/constants.js.map +1 -1
  41. package/dist/telemetry/telemetry.js +28 -21
  42. package/dist/telemetry/telemetry.js.map +1 -1
  43. package/dist/tools/atlas/atlasTool.js +3 -3
  44. package/dist/tools/atlas/atlasTool.js.map +1 -1
  45. package/dist/tools/atlas/connect/connectCluster.js +198 -0
  46. package/dist/tools/atlas/connect/connectCluster.js.map +1 -0
  47. package/dist/tools/atlas/create/createAccessList.js +9 -10
  48. package/dist/tools/atlas/create/createAccessList.js.map +1 -1
  49. package/dist/tools/atlas/create/createDBUser.js +3 -1
  50. package/dist/tools/atlas/create/createDBUser.js.map +1 -1
  51. package/dist/tools/atlas/create/createFreeCluster.js +2 -0
  52. package/dist/tools/atlas/create/createFreeCluster.js.map +1 -1
  53. package/dist/tools/atlas/create/createProject.js.map +1 -1
  54. package/dist/tools/atlas/read/inspectAccessList.js.map +1 -1
  55. package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
  56. package/dist/tools/atlas/read/listAlerts.js.map +1 -1
  57. package/dist/tools/atlas/read/listClusters.js.map +1 -1
  58. package/dist/tools/atlas/read/listDBUsers.js.map +1 -1
  59. package/dist/tools/atlas/read/listOrgs.js.map +1 -1
  60. package/dist/tools/atlas/read/listProjects.js.map +1 -1
  61. package/dist/tools/atlas/tools.js +1 -1
  62. package/dist/tools/atlas/tools.js.map +1 -1
  63. package/dist/tools/mongodb/{metadata → connect}/connect.js +7 -4
  64. package/dist/tools/mongodb/connect/connect.js.map +1 -0
  65. package/dist/tools/mongodb/create/createCollection.js.map +1 -1
  66. package/dist/tools/mongodb/create/createIndex.js +1 -1
  67. package/dist/tools/mongodb/create/createIndex.js.map +1 -1
  68. package/dist/tools/mongodb/create/insertMany.js +1 -1
  69. package/dist/tools/mongodb/create/insertMany.js.map +1 -1
  70. package/dist/tools/mongodb/delete/deleteMany.js +2 -1
  71. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
  72. package/dist/tools/mongodb/delete/dropCollection.js.map +1 -1
  73. package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -1
  74. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
  75. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
  76. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
  77. package/dist/tools/mongodb/metadata/explain.js +1 -1
  78. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  79. package/dist/tools/mongodb/metadata/listCollections.js.map +1 -1
  80. package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -1
  81. package/dist/tools/mongodb/metadata/logs.js.map +1 -1
  82. package/dist/tools/mongodb/mongodbTool.js +37 -10
  83. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  84. package/dist/tools/mongodb/read/aggregate.js +1 -1
  85. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  86. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
  87. package/dist/tools/mongodb/read/count.js +2 -1
  88. package/dist/tools/mongodb/read/count.js.map +1 -1
  89. package/dist/tools/mongodb/read/find.js +7 -4
  90. package/dist/tools/mongodb/read/find.js.map +1 -1
  91. package/dist/tools/mongodb/tools.js +1 -1
  92. package/dist/tools/mongodb/tools.js.map +1 -1
  93. package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
  94. package/dist/tools/mongodb/update/updateMany.js +4 -2
  95. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  96. package/dist/tools/tool.js +8 -5
  97. package/dist/tools/tool.js.map +1 -1
  98. package/dist/transports/base.js +26 -0
  99. package/dist/transports/base.js.map +1 -0
  100. package/dist/{helpers/EJsonTransport.js → transports/stdio.js} +24 -2
  101. package/dist/transports/stdio.js.map +1 -0
  102. package/dist/transports/streamableHttp.js +140 -0
  103. package/dist/transports/streamableHttp.js.map +1 -0
  104. package/eslint.config.js +13 -4
  105. package/package.json +43 -33
  106. package/resources/test-summary-template.html +415 -0
  107. package/scripts/accuracy/generateTestSummary.ts +335 -0
  108. package/scripts/accuracy/runAccuracyTests.sh +45 -0
  109. package/scripts/accuracy/updateAccuracyRunStatus.ts +21 -0
  110. package/src/common/atlas/accessListUtils.ts +54 -0
  111. package/src/common/atlas/apiClient.ts +25 -6
  112. package/src/common/atlas/cluster.ts +1 -1
  113. package/src/{config.ts → common/config.ts} +14 -2
  114. package/src/{logger.ts → common/logger.ts} +21 -23
  115. package/src/common/managedTimeout.ts +27 -0
  116. package/src/{session.ts → common/session.ts} +24 -26
  117. package/src/common/sessionStore.ts +111 -0
  118. package/src/helpers/container.ts +35 -0
  119. package/src/helpers/indexCheck.ts +1 -1
  120. package/src/index.ts +30 -40
  121. package/src/server.ts +54 -10
  122. package/src/telemetry/constants.ts +1 -1
  123. package/src/telemetry/telemetry.ts +34 -26
  124. package/src/telemetry/types.ts +2 -0
  125. package/src/tools/atlas/atlasTool.ts +4 -4
  126. package/src/tools/atlas/connect/connectCluster.ts +259 -0
  127. package/src/tools/atlas/create/createAccessList.ts +15 -13
  128. package/src/tools/atlas/create/createDBUser.ts +5 -3
  129. package/src/tools/atlas/create/createFreeCluster.ts +4 -2
  130. package/src/tools/atlas/create/createProject.ts +2 -2
  131. package/src/tools/atlas/read/inspectAccessList.ts +2 -2
  132. package/src/tools/atlas/read/inspectCluster.ts +2 -2
  133. package/src/tools/atlas/read/listAlerts.ts +2 -2
  134. package/src/tools/atlas/read/listClusters.ts +2 -2
  135. package/src/tools/atlas/read/listDBUsers.ts +2 -2
  136. package/src/tools/atlas/read/listOrgs.ts +2 -2
  137. package/src/tools/atlas/read/listProjects.ts +2 -2
  138. package/src/tools/atlas/tools.ts +1 -1
  139. package/src/tools/mongodb/{metadata → connect}/connect.ts +12 -9
  140. package/src/tools/mongodb/create/createCollection.ts +2 -2
  141. package/src/tools/mongodb/create/createIndex.ts +3 -3
  142. package/src/tools/mongodb/create/insertMany.ts +3 -3
  143. package/src/tools/mongodb/delete/deleteMany.ts +4 -3
  144. package/src/tools/mongodb/delete/dropCollection.ts +2 -2
  145. package/src/tools/mongodb/delete/dropDatabase.ts +2 -2
  146. package/src/tools/mongodb/metadata/collectionSchema.ts +2 -2
  147. package/src/tools/mongodb/metadata/collectionStorageSize.ts +2 -2
  148. package/src/tools/mongodb/metadata/dbStats.ts +2 -2
  149. package/src/tools/mongodb/metadata/explain.ts +3 -3
  150. package/src/tools/mongodb/metadata/listCollections.ts +2 -2
  151. package/src/tools/mongodb/metadata/listDatabases.ts +2 -2
  152. package/src/tools/mongodb/metadata/logs.ts +2 -2
  153. package/src/tools/mongodb/mongodbTool.ts +50 -14
  154. package/src/tools/mongodb/read/aggregate.ts +3 -3
  155. package/src/tools/mongodb/read/collectionIndexes.ts +2 -2
  156. package/src/tools/mongodb/read/count.ts +4 -3
  157. package/src/tools/mongodb/read/find.ts +11 -6
  158. package/src/tools/mongodb/tools.ts +1 -1
  159. package/src/tools/mongodb/update/renameCollection.ts +2 -2
  160. package/src/tools/mongodb/update/updateMany.ts +6 -4
  161. package/src/tools/tool.ts +18 -13
  162. package/src/transports/base.ts +34 -0
  163. package/src/{helpers/EJsonTransport.ts → transports/stdio.ts} +30 -1
  164. package/src/transports/streamableHttp.ts +178 -0
  165. package/tests/accuracy/aggregate.test.ts +27 -0
  166. package/tests/accuracy/collectionIndexes.test.ts +40 -0
  167. package/tests/accuracy/collectionSchema.test.ts +28 -0
  168. package/tests/accuracy/collectionStorageSize.test.ts +41 -0
  169. package/tests/accuracy/count.test.ts +44 -0
  170. package/tests/accuracy/createCollection.test.ts +46 -0
  171. package/tests/accuracy/createIndex.test.ts +37 -0
  172. package/tests/accuracy/dbStats.test.ts +15 -0
  173. package/tests/accuracy/deleteMany.test.ts +44 -0
  174. package/tests/accuracy/dropCollection.test.ts +74 -0
  175. package/tests/accuracy/dropDatabase.test.ts +41 -0
  176. package/tests/accuracy/explain.test.ts +73 -0
  177. package/tests/accuracy/find.test.ts +114 -0
  178. package/tests/accuracy/insertMany.test.ts +48 -0
  179. package/tests/accuracy/listCollections.test.ts +60 -0
  180. package/tests/accuracy/listDatabases.test.ts +31 -0
  181. package/tests/accuracy/logs.test.ts +28 -0
  182. package/tests/accuracy/renameCollection.test.ts +31 -0
  183. package/tests/accuracy/sdk/accuracyResultStorage/diskStorage.ts +189 -0
  184. package/tests/accuracy/sdk/accuracyResultStorage/getAccuracyResultStorage.ts +11 -0
  185. package/tests/accuracy/sdk/accuracyResultStorage/mongodbStorage.ts +151 -0
  186. package/tests/accuracy/sdk/accuracyResultStorage/resultStorage.ts +117 -0
  187. package/tests/accuracy/sdk/accuracyScorer.ts +93 -0
  188. package/tests/accuracy/sdk/accuracyTestingClient.ts +94 -0
  189. package/tests/accuracy/sdk/agent.ts +56 -0
  190. package/tests/accuracy/sdk/constants.ts +26 -0
  191. package/tests/accuracy/sdk/describeAccuracyTests.ts +126 -0
  192. package/tests/accuracy/sdk/gitInfo.ts +7 -0
  193. package/tests/accuracy/sdk/matcher.ts +193 -0
  194. package/tests/accuracy/sdk/models.ts +95 -0
  195. package/tests/accuracy/test-data-dumps/comics.books.json +417 -0
  196. package/tests/accuracy/test-data-dumps/comics.characters.json +402 -0
  197. package/tests/accuracy/test-data-dumps/mflix.movies.json +496 -0
  198. package/tests/accuracy/test-data-dumps/mflix.shows.json +572 -0
  199. package/tests/accuracy/updateMany.test.ts +42 -0
  200. package/tests/integration/helpers.ts +9 -9
  201. package/tests/integration/indexCheck.test.ts +1 -0
  202. package/tests/integration/server.test.ts +1 -0
  203. package/tests/integration/telemetry.test.ts +4 -3
  204. package/tests/integration/tools/atlas/accessLists.test.ts +22 -2
  205. package/tests/integration/tools/atlas/alerts.test.ts +3 -2
  206. package/tests/integration/tools/atlas/atlasHelpers.ts +3 -0
  207. package/tests/integration/tools/atlas/clusters.test.ts +68 -16
  208. package/tests/integration/tools/atlas/dbUsers.test.ts +14 -1
  209. package/tests/integration/tools/atlas/orgs.test.ts +2 -1
  210. package/tests/integration/tools/atlas/projects.test.ts +4 -3
  211. package/tests/integration/tools/mongodb/{metadata → connect}/connect.test.ts +34 -3
  212. package/tests/integration/tools/mongodb/create/createCollection.test.ts +1 -0
  213. package/tests/integration/tools/mongodb/create/createIndex.test.ts +1 -0
  214. package/tests/integration/tools/mongodb/create/insertMany.test.ts +1 -0
  215. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +1 -0
  216. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +1 -1
  217. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +1 -0
  218. package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +1 -0
  219. package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +1 -0
  220. package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +1 -0
  221. package/tests/integration/tools/mongodb/metadata/explain.test.ts +1 -0
  222. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +1 -0
  223. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +3 -2
  224. package/tests/integration/tools/mongodb/metadata/logs.test.ts +1 -0
  225. package/tests/integration/tools/mongodb/mongodbHelpers.ts +66 -2
  226. package/tests/integration/tools/mongodb/read/aggregate.test.ts +2 -1
  227. package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +1 -0
  228. package/tests/integration/tools/mongodb/read/count.test.ts +1 -0
  229. package/tests/integration/tools/mongodb/read/find.test.ts +2 -1
  230. package/tests/integration/tools/mongodb/update/renameCollection.test.ts +1 -0
  231. package/tests/integration/tools/mongodb/update/updateMany.test.ts +1 -0
  232. package/tests/integration/transports/stdio.test.ts +40 -0
  233. package/tests/integration/transports/streamableHttp.test.ts +56 -0
  234. package/tests/matchers/toIncludeSameMembers.test.ts +59 -0
  235. package/tests/matchers/toIncludeSameMembers.ts +12 -0
  236. package/tests/setup.ts +7 -0
  237. package/tests/unit/accessListUtils.test.ts +39 -0
  238. package/tests/unit/accuracyScorer.test.ts +390 -0
  239. package/tests/unit/{apiClient.test.ts → common/apiClient.test.ts} +15 -15
  240. package/tests/unit/common/managedTimeout.test.ts +67 -0
  241. package/tests/unit/{session.test.ts → common/session.test.ts} +7 -12
  242. package/tests/unit/{indexCheck.test.ts → helpers/indexCheck.test.ts} +2 -1
  243. package/tests/unit/telemetry.test.ts +52 -42
  244. package/tests/unit/{EJsonTransport.test.ts → transports/stdio.test.ts} +4 -4
  245. package/tests/vitest.d.ts +11 -0
  246. package/tsconfig.json +0 -1
  247. package/{tsconfig.jest.json → tsconfig.test.json} +1 -2
  248. package/vitest.config.ts +41 -0
  249. package/dist/common/atlas/generatePassword.js.map +0 -1
  250. package/dist/config.js.map +0 -1
  251. package/dist/errors.js.map +0 -1
  252. package/dist/helpers/EJsonTransport.js.map +0 -1
  253. package/dist/helpers/packageInfo.js.map +0 -1
  254. package/dist/logger.js.map +0 -1
  255. package/dist/session.js.map +0 -1
  256. package/dist/tools/atlas/metadata/connectCluster.js +0 -100
  257. package/dist/tools/atlas/metadata/connectCluster.js.map +0 -1
  258. package/dist/tools/mongodb/metadata/connect.js.map +0 -1
  259. package/global.d.ts +0 -1
  260. package/jest.config.cjs +0 -22
  261. package/src/tools/atlas/metadata/connectCluster.ts +0 -121
  262. /package/dist/{errors.js → common/errors.js} +0 -0
  263. /package/dist/{helpers → common}/packageInfo.js +0 -0
  264. /package/dist/{common/atlas → helpers}/generatePassword.js +0 -0
  265. /package/src/{errors.ts → common/errors.ts} +0 -0
  266. /package/src/{helpers → common}/packageInfo.ts +0 -0
  267. /package/src/{common/atlas → helpers}/generatePassword.ts +0 -0
@@ -0,0 +1,259 @@
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 { generateSecurePassword } from "../../../helpers/generatePassword.js";
6
+ import logger, { LogId } from "../../../common/logger.js";
7
+ import { inspectCluster } from "../../../common/atlas/cluster.js";
8
+ import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
9
+
10
+ const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours
11
+
12
+ function sleep(ms: number): Promise<void> {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
15
+
16
+ export class ConnectClusterTool extends AtlasToolBase {
17
+ public name = "atlas-connect-cluster";
18
+ protected description = "Connect to MongoDB Atlas cluster";
19
+ public operationType: OperationType = "connect";
20
+ protected argsShape = {
21
+ projectId: z.string().describe("Atlas project ID"),
22
+ clusterName: z.string().describe("Atlas cluster name"),
23
+ };
24
+
25
+ private async queryConnection(
26
+ projectId: string,
27
+ clusterName: string
28
+ ): Promise<"connected" | "disconnected" | "connecting" | "connected-to-other-cluster" | "unknown"> {
29
+ if (!this.session.connectedAtlasCluster) {
30
+ if (this.session.serviceProvider) {
31
+ return "connected-to-other-cluster";
32
+ }
33
+ return "disconnected";
34
+ }
35
+
36
+ if (
37
+ this.session.connectedAtlasCluster.projectId !== projectId ||
38
+ this.session.connectedAtlasCluster.clusterName !== clusterName
39
+ ) {
40
+ return "connected-to-other-cluster";
41
+ }
42
+
43
+ if (!this.session.serviceProvider) {
44
+ return "connecting";
45
+ }
46
+
47
+ try {
48
+ await this.session.serviceProvider.runCommand("admin", {
49
+ ping: 1,
50
+ });
51
+
52
+ return "connected";
53
+ } catch (err: unknown) {
54
+ const error = err instanceof Error ? err : new Error(String(err));
55
+ logger.debug(
56
+ LogId.atlasConnectFailure,
57
+ "atlas-connect-cluster",
58
+ `error querying cluster: ${error.message}`
59
+ );
60
+ return "unknown";
61
+ }
62
+ }
63
+
64
+ private async prepareClusterConnection(projectId: string, clusterName: string): Promise<string> {
65
+ const cluster = await inspectCluster(this.session.apiClient, projectId, clusterName);
66
+
67
+ if (!cluster.connectionString) {
68
+ throw new Error("Connection string not available");
69
+ }
70
+
71
+ const username = `mcpUser${Math.floor(Math.random() * 100000)}`;
72
+ const password = await generateSecurePassword();
73
+
74
+ const expiryDate = new Date(Date.now() + EXPIRY_MS);
75
+
76
+ const readOnly =
77
+ this.config.readOnly ||
78
+ (this.config.disabledTools?.includes("create") &&
79
+ this.config.disabledTools?.includes("update") &&
80
+ this.config.disabledTools?.includes("delete") &&
81
+ !this.config.disabledTools?.includes("read") &&
82
+ !this.config.disabledTools?.includes("metadata"));
83
+
84
+ const roleName = readOnly ? "readAnyDatabase" : "readWriteAnyDatabase";
85
+
86
+ await this.session.apiClient.createDatabaseUser({
87
+ params: {
88
+ path: {
89
+ groupId: projectId,
90
+ },
91
+ },
92
+ body: {
93
+ databaseName: "admin",
94
+ groupId: projectId,
95
+ roles: [
96
+ {
97
+ roleName,
98
+ databaseName: "admin",
99
+ },
100
+ ],
101
+ scopes: [{ type: "CLUSTER", name: clusterName }],
102
+ username,
103
+ password,
104
+ awsIAMType: "NONE",
105
+ ldapAuthType: "NONE",
106
+ oidcAuthType: "NONE",
107
+ x509Type: "NONE",
108
+ deleteAfterDate: expiryDate.toISOString(),
109
+ },
110
+ });
111
+
112
+ this.session.connectedAtlasCluster = {
113
+ username,
114
+ projectId,
115
+ clusterName,
116
+ expiryDate,
117
+ };
118
+
119
+ const cn = new URL(cluster.connectionString);
120
+ cn.username = username;
121
+ cn.password = password;
122
+ cn.searchParams.set("authSource", "admin");
123
+ return cn.toString();
124
+ }
125
+
126
+ private async connectToCluster(projectId: string, clusterName: string, connectionString: string): Promise<void> {
127
+ let lastError: Error | undefined = undefined;
128
+
129
+ logger.debug(
130
+ LogId.atlasConnectAttempt,
131
+ "atlas-connect-cluster",
132
+ `attempting to connect to cluster: ${this.session.connectedAtlasCluster?.clusterName}`
133
+ );
134
+
135
+ // try to connect for about 5 minutes
136
+ for (let i = 0; i < 600; i++) {
137
+ if (
138
+ !this.session.connectedAtlasCluster ||
139
+ this.session.connectedAtlasCluster.projectId != projectId ||
140
+ this.session.connectedAtlasCluster.clusterName != clusterName
141
+ ) {
142
+ throw new Error("Cluster connection aborted");
143
+ }
144
+
145
+ try {
146
+ lastError = undefined;
147
+
148
+ await this.session.connectToMongoDB(connectionString, this.config.connectOptions);
149
+ break;
150
+ } catch (err: unknown) {
151
+ const error = err instanceof Error ? err : new Error(String(err));
152
+
153
+ lastError = error;
154
+
155
+ logger.debug(
156
+ LogId.atlasConnectFailure,
157
+ "atlas-connect-cluster",
158
+ `error connecting to cluster: ${error.message}`
159
+ );
160
+
161
+ await sleep(500); // wait for 500ms before retrying
162
+ }
163
+ }
164
+
165
+ if (lastError) {
166
+ if (
167
+ this.session.connectedAtlasCluster?.projectId == projectId &&
168
+ this.session.connectedAtlasCluster?.clusterName == clusterName &&
169
+ this.session.connectedAtlasCluster?.username
170
+ ) {
171
+ void this.session.apiClient
172
+ .deleteDatabaseUser({
173
+ params: {
174
+ path: {
175
+ groupId: this.session.connectedAtlasCluster.projectId,
176
+ username: this.session.connectedAtlasCluster.username,
177
+ databaseName: "admin",
178
+ },
179
+ },
180
+ })
181
+ .catch((err: unknown) => {
182
+ const error = err instanceof Error ? err : new Error(String(err));
183
+ logger.debug(
184
+ LogId.atlasConnectFailure,
185
+ "atlas-connect-cluster",
186
+ `error deleting database user: ${error.message}`
187
+ );
188
+ });
189
+ }
190
+ this.session.connectedAtlasCluster = undefined;
191
+ throw lastError;
192
+ }
193
+
194
+ logger.debug(
195
+ LogId.atlasConnectSucceeded,
196
+ "atlas-connect-cluster",
197
+ `connected to cluster: ${this.session.connectedAtlasCluster?.clusterName}`
198
+ );
199
+ }
200
+
201
+ protected async execute({ projectId, clusterName }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
202
+ await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
203
+ for (let i = 0; i < 60; i++) {
204
+ const state = await this.queryConnection(projectId, clusterName);
205
+ switch (state) {
206
+ case "connected": {
207
+ return {
208
+ content: [
209
+ {
210
+ type: "text",
211
+ text: `Connected to cluster "${clusterName}".`,
212
+ },
213
+ ],
214
+ };
215
+ }
216
+ case "connecting":
217
+ case "unknown": {
218
+ break;
219
+ }
220
+ case "connected-to-other-cluster":
221
+ case "disconnected":
222
+ default: {
223
+ await this.session.disconnect();
224
+ const connectionString = await this.prepareClusterConnection(projectId, clusterName);
225
+
226
+ // try to connect for about 5 minutes asynchronously
227
+ void this.connectToCluster(projectId, clusterName, connectionString).catch((err: unknown) => {
228
+ const error = err instanceof Error ? err : new Error(String(err));
229
+ logger.error(
230
+ LogId.atlasConnectFailure,
231
+ "atlas-connect-cluster",
232
+ `error connecting to cluster: ${error.message}`
233
+ );
234
+ });
235
+ break;
236
+ }
237
+ }
238
+
239
+ await sleep(500);
240
+ }
241
+
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text" as const,
246
+ text: `Attempting to connect to cluster "${clusterName}"...`,
247
+ },
248
+ {
249
+ type: "text" as const,
250
+ text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`,
251
+ },
252
+ {
253
+ type: "text" as const,
254
+ text: `Warning: Make sure your IP address was enabled in the allow list setting of the Atlas cluster.`,
255
+ },
256
+ ],
257
+ };
258
+ }
259
+ }
@@ -2,13 +2,12 @@ import { z } from "zod";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
-
6
- const DEFAULT_COMMENT = "Added by Atlas MCP";
5
+ import { makeCurrentIpAccessListEntry, DEFAULT_ACCESS_LIST_COMMENT } from "../../../common/atlas/accessListUtils.js";
7
6
 
8
7
  export class CreateAccessListTool extends AtlasToolBase {
9
- protected name = "atlas-create-access-list";
8
+ public name = "atlas-create-access-list";
10
9
  protected description = "Allow Ip/CIDR ranges to access your MongoDB Atlas clusters.";
11
- protected operationType: OperationType = "create";
10
+ public operationType: OperationType = "create";
12
11
  protected argsShape = {
13
12
  projectId: z.string().describe("Atlas project ID"),
14
13
  ipAddresses: z
@@ -17,7 +16,11 @@ export class CreateAccessListTool extends AtlasToolBase {
17
16
  .optional(),
18
17
  cidrBlocks: z.array(z.string().cidr()).describe("CIDR blocks to allow access from").optional(),
19
18
  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(),
19
+ comment: z
20
+ .string()
21
+ .describe("Comment for the access list entries")
22
+ .default(DEFAULT_ACCESS_LIST_COMMENT)
23
+ .optional(),
21
24
  };
22
25
 
23
26
  protected async execute({
@@ -34,23 +37,22 @@ export class CreateAccessListTool extends AtlasToolBase {
34
37
  const ipInputs = (ipAddresses || []).map((ipAddress) => ({
35
38
  groupId: projectId,
36
39
  ipAddress,
37
- comment: comment || DEFAULT_COMMENT,
40
+ comment: comment || DEFAULT_ACCESS_LIST_COMMENT,
38
41
  }));
39
42
 
40
43
  if (currentIpAddress) {
41
- const currentIp = await this.session.apiClient.getIpInfo();
42
- const input = {
43
- groupId: projectId,
44
- ipAddress: currentIp.currentIpv4Address,
45
- comment: comment || DEFAULT_COMMENT,
46
- };
44
+ const input = await makeCurrentIpAccessListEntry(
45
+ this.session.apiClient,
46
+ projectId,
47
+ comment || DEFAULT_ACCESS_LIST_COMMENT
48
+ );
47
49
  ipInputs.push(input);
48
50
  }
49
51
 
50
52
  const cidrInputs = (cidrBlocks || []).map((cidrBlock) => ({
51
53
  groupId: projectId,
52
54
  cidrBlock,
53
- comment: comment || DEFAULT_COMMENT,
55
+ comment: comment || DEFAULT_ACCESS_LIST_COMMENT,
54
56
  }));
55
57
 
56
58
  const inputs = [...ipInputs, ...cidrInputs];
@@ -3,12 +3,13 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { CloudDatabaseUser, DatabaseUserRole } from "../../../common/atlas/openapi.js";
6
- import { generateSecurePassword } from "../../../common/atlas/generatePassword.js";
6
+ import { generateSecurePassword } from "../../../helpers/generatePassword.js";
7
+ import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
7
8
 
8
9
  export class CreateDBUserTool extends AtlasToolBase {
9
- protected name = "atlas-create-db-user";
10
+ public name = "atlas-create-db-user";
10
11
  protected description = "Create an MongoDB Atlas database user";
11
- protected operationType: OperationType = "create";
12
+ public operationType: OperationType = "create";
12
13
  protected argsShape = {
13
14
  projectId: z.string().describe("Atlas project ID"),
14
15
  username: z.string().describe("Username for the new user"),
@@ -44,6 +45,7 @@ export class CreateDBUserTool extends AtlasToolBase {
44
45
  roles,
45
46
  clusters,
46
47
  }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
48
+ await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
47
49
  const shouldGeneratePassword = !password;
48
50
  if (shouldGeneratePassword) {
49
51
  password = await generateSecurePassword();
@@ -3,11 +3,12 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { ClusterDescription20240805 } from "../../../common/atlas/openapi.js";
6
+ import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
6
7
 
7
8
  export class CreateFreeClusterTool extends AtlasToolBase {
8
- protected name = "atlas-create-free-cluster";
9
+ public name = "atlas-create-free-cluster";
9
10
  protected description = "Create a free MongoDB Atlas cluster";
10
- protected operationType: OperationType = "create";
11
+ public operationType: OperationType = "create";
11
12
  protected argsShape = {
12
13
  projectId: z.string().describe("Atlas project ID to create the cluster in"),
13
14
  name: z.string().describe("Name of the cluster"),
@@ -37,6 +38,7 @@ export class CreateFreeClusterTool extends AtlasToolBase {
37
38
  terminationProtectionEnabled: false,
38
39
  } as unknown as ClusterDescription20240805;
39
40
 
41
+ await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
40
42
  await this.session.apiClient.createCluster({
41
43
  params: {
42
44
  path: {
@@ -5,9 +5,9 @@ import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { Group } from "../../../common/atlas/openapi.js";
6
6
 
7
7
  export class CreateProjectTool extends AtlasToolBase {
8
- protected name = "atlas-create-project";
8
+ public name = "atlas-create-project";
9
9
  protected description = "Create a MongoDB Atlas project";
10
- protected operationType: OperationType = "create";
10
+ public operationType: OperationType = "create";
11
11
  protected argsShape = {
12
12
  projectName: z.string().optional().describe("Name for the new project"),
13
13
  organizationId: z.string().optional().describe("Organization ID for the new project"),
@@ -4,9 +4,9 @@ import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
 
6
6
  export class InspectAccessListTool extends AtlasToolBase {
7
- protected name = "atlas-inspect-access-list";
7
+ public name = "atlas-inspect-access-list";
8
8
  protected description = "Inspect Ip/CIDR ranges with access to your MongoDB Atlas clusters.";
9
- protected operationType: OperationType = "read";
9
+ public operationType: OperationType = "read";
10
10
  protected argsShape = {
11
11
  projectId: z.string().describe("Atlas project ID"),
12
12
  };
@@ -5,9 +5,9 @@ import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { Cluster, inspectCluster } from "../../../common/atlas/cluster.js";
6
6
 
7
7
  export class InspectClusterTool extends AtlasToolBase {
8
- protected name = "atlas-inspect-cluster";
8
+ public name = "atlas-inspect-cluster";
9
9
  protected description = "Inspect MongoDB Atlas cluster";
10
- protected operationType: OperationType = "read";
10
+ public operationType: OperationType = "read";
11
11
  protected argsShape = {
12
12
  projectId: z.string().describe("Atlas project ID"),
13
13
  clusterName: z.string().describe("Atlas cluster name"),
@@ -4,9 +4,9 @@ import { AtlasToolBase } from "../atlasTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
 
6
6
  export class ListAlertsTool extends AtlasToolBase {
7
- protected name = "atlas-list-alerts";
7
+ public name = "atlas-list-alerts";
8
8
  protected description = "List MongoDB Atlas alerts";
9
- protected operationType: OperationType = "read";
9
+ public operationType: OperationType = "read";
10
10
  protected argsShape = {
11
11
  projectId: z.string().describe("Atlas project ID to list alerts for"),
12
12
  };
@@ -11,9 +11,9 @@ import {
11
11
  import { formatCluster, formatFlexCluster } from "../../../common/atlas/cluster.js";
12
12
 
13
13
  export class ListClustersTool extends AtlasToolBase {
14
- protected name = "atlas-list-clusters";
14
+ public name = "atlas-list-clusters";
15
15
  protected description = "List MongoDB Atlas clusters";
16
- protected operationType: OperationType = "read";
16
+ public operationType: OperationType = "read";
17
17
  protected argsShape = {
18
18
  projectId: z.string().describe("Atlas project ID to filter clusters").optional(),
19
19
  };
@@ -5,9 +5,9 @@ import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { DatabaseUserRole, UserScope } from "../../../common/atlas/openapi.js";
6
6
 
7
7
  export class ListDBUsersTool extends AtlasToolBase {
8
- protected name = "atlas-list-db-users";
8
+ public name = "atlas-list-db-users";
9
9
  protected description = "List MongoDB Atlas database users";
10
- protected operationType: OperationType = "read";
10
+ public operationType: OperationType = "read";
11
11
  protected argsShape = {
12
12
  projectId: z.string().describe("Atlas project ID to filter DB users"),
13
13
  };
@@ -3,9 +3,9 @@ import { AtlasToolBase } from "../atlasTool.js";
3
3
  import { OperationType } from "../../tool.js";
4
4
 
5
5
  export class ListOrganizationsTool extends AtlasToolBase {
6
- protected name = "atlas-list-orgs";
6
+ public name = "atlas-list-orgs";
7
7
  protected description = "List MongoDB Atlas organizations";
8
- protected operationType: OperationType = "read";
8
+ public operationType: OperationType = "read";
9
9
  protected argsShape = {};
10
10
 
11
11
  protected async execute(): Promise<CallToolResult> {
@@ -5,9 +5,9 @@ import { z } from "zod";
5
5
  import { ToolArgs } from "../../tool.js";
6
6
 
7
7
  export class ListProjectsTool extends AtlasToolBase {
8
- protected name = "atlas-list-projects";
8
+ public name = "atlas-list-projects";
9
9
  protected description = "List MongoDB Atlas projects";
10
- protected operationType: OperationType = "read";
10
+ public operationType: OperationType = "read";
11
11
  protected argsShape = {
12
12
  orgId: z.string().describe("Atlas organization ID to filter projects").optional(),
13
13
  };
@@ -8,7 +8,7 @@ import { ListDBUsersTool } from "./read/listDBUsers.js";
8
8
  import { CreateDBUserTool } from "./create/createDBUser.js";
9
9
  import { CreateProjectTool } from "./create/createProject.js";
10
10
  import { ListOrganizationsTool } from "./read/listOrgs.js";
11
- import { ConnectClusterTool } from "./metadata/connectCluster.js";
11
+ import { ConnectClusterTool } from "./connect/connectCluster.js";
12
12
  import { ListAlertsTool } from "./read/listAlerts.js";
13
13
 
14
14
  export const AtlasTools = [
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
5
  import assert from "assert";
7
- import { UserConfig } from "../../../config.js";
6
+ import { UserConfig } from "../../../common/config.js";
8
7
  import { Telemetry } from "../../../telemetry/telemetry.js";
9
- import { Session } from "../../../session.js";
8
+ import { Session } from "../../../common/session.js";
9
+ import { Server } from "../../../server.js";
10
10
 
11
11
  const disconnectedSchema = z
12
12
  .object({
@@ -33,7 +33,7 @@ const connectedDescription =
33
33
  const disconnectedDescription = "Connect to a MongoDB instance";
34
34
 
35
35
  export class ConnectTool extends MongoDBToolBase {
36
- protected name: typeof connectedName | typeof disconnectedName = disconnectedName;
36
+ public name: typeof connectedName | typeof disconnectedName = disconnectedName;
37
37
  protected description: typeof connectedDescription | typeof disconnectedDescription = disconnectedDescription;
38
38
 
39
39
  // Here the default is empty just to trigger registration, but we're going to override it with the correct
@@ -42,11 +42,11 @@ export class ConnectTool extends MongoDBToolBase {
42
42
  connectionString: z.string().optional(),
43
43
  };
44
44
 
45
- protected operationType: OperationType = "metadata";
45
+ public operationType: OperationType = "connect";
46
46
 
47
47
  constructor(session: Session, config: UserConfig, telemetry: Telemetry) {
48
48
  super(session, config, telemetry);
49
- session.on("close", () => {
49
+ session.on("disconnect", () => {
50
50
  this.updateMetadata();
51
51
  });
52
52
  }
@@ -72,10 +72,13 @@ export class ConnectTool extends MongoDBToolBase {
72
72
  };
73
73
  }
74
74
 
75
- public register(server: McpServer): void {
76
- super.register(server);
75
+ public register(server: Server): boolean {
76
+ if (super.register(server)) {
77
+ this.updateMetadata();
78
+ return true;
79
+ }
77
80
 
78
- this.updateMetadata();
81
+ return false;
79
82
  }
80
83
 
81
84
  private updateMetadata(): void {
@@ -3,12 +3,12 @@ import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
3
3
  import { OperationType, ToolArgs } from "../../tool.js";
4
4
 
5
5
  export class CreateCollectionTool extends MongoDBToolBase {
6
- protected name = "create-collection";
6
+ public name = "create-collection";
7
7
  protected description =
8
8
  "Creates a new collection in a database. If the database doesn't exist, it will be created automatically.";
9
9
  protected argsShape = DbOperationArgs;
10
10
 
11
- protected operationType: OperationType = "create";
11
+ public operationType: OperationType = "create";
12
12
 
13
13
  protected async execute({ collection, database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
14
14
  const provider = await this.ensureConnected();
@@ -5,15 +5,15 @@ import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { IndexDirection } from "mongodb";
6
6
 
7
7
  export class CreateIndexTool extends MongoDBToolBase {
8
- protected name = "create-index";
8
+ public name = "create-index";
9
9
  protected description = "Create an index for a collection";
10
10
  protected argsShape = {
11
11
  ...DbOperationArgs,
12
- keys: z.record(z.string(), z.custom<IndexDirection>()).describe("The index definition"),
12
+ keys: z.object({}).catchall(z.custom<IndexDirection>()).describe("The index definition"),
13
13
  name: z.string().optional().describe("The name of the index"),
14
14
  };
15
15
 
16
- protected operationType: OperationType = "create";
16
+ public operationType: OperationType = "create";
17
17
 
18
18
  protected async execute({
19
19
  database,
@@ -4,17 +4,17 @@ import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
 
6
6
  export class InsertManyTool extends MongoDBToolBase {
7
- protected name = "insert-many";
7
+ public name = "insert-many";
8
8
  protected description = "Insert an array of documents into a MongoDB collection";
9
9
  protected argsShape = {
10
10
  ...DbOperationArgs,
11
11
  documents: z
12
- .array(z.record(z.string(), z.unknown()).describe("An individual MongoDB document"))
12
+ .array(z.object({}).passthrough().describe("An individual MongoDB document"))
13
13
  .describe(
14
14
  "The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()"
15
15
  ),
16
16
  };
17
- protected operationType: OperationType = "create";
17
+ public operationType: OperationType = "create";
18
18
 
19
19
  protected async execute({
20
20
  database,
@@ -5,18 +5,19 @@ import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { checkIndexUsage } from "../../../helpers/indexCheck.js";
6
6
 
7
7
  export class DeleteManyTool extends MongoDBToolBase {
8
- protected name = "delete-many";
8
+ public name = "delete-many";
9
9
  protected description = "Removes all documents that match the filter from a MongoDB collection";
10
10
  protected argsShape = {
11
11
  ...DbOperationArgs,
12
12
  filter: z
13
- .record(z.string(), z.unknown())
13
+ .object({})
14
+ .passthrough()
14
15
  .optional()
15
16
  .describe(
16
17
  "The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()"
17
18
  ),
18
19
  };
19
- protected operationType: OperationType = "delete";
20
+ public operationType: OperationType = "delete";
20
21
 
21
22
  protected async execute({
22
23
  database,
@@ -3,13 +3,13 @@ import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
3
3
  import { ToolArgs, OperationType } from "../../tool.js";
4
4
 
5
5
  export class DropCollectionTool extends MongoDBToolBase {
6
- protected name = "drop-collection";
6
+ public name = "drop-collection";
7
7
  protected description =
8
8
  "Removes a collection or view from the database. The method also removes any indexes associated with the dropped collection.";
9
9
  protected argsShape = {
10
10
  ...DbOperationArgs,
11
11
  };
12
- protected operationType: OperationType = "delete";
12
+ public operationType: OperationType = "delete";
13
13
 
14
14
  protected async execute({ database, collection }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
15
15
  const provider = await this.ensureConnected();
@@ -3,12 +3,12 @@ import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
3
3
  import { ToolArgs, OperationType } from "../../tool.js";
4
4
 
5
5
  export class DropDatabaseTool extends MongoDBToolBase {
6
- protected name = "drop-database";
6
+ public name = "drop-database";
7
7
  protected description = "Removes the specified database, deleting the associated data files";
8
8
  protected argsShape = {
9
9
  database: DbOperationArgs.database,
10
10
  };
11
- protected operationType: OperationType = "delete";
11
+ public operationType: OperationType = "delete";
12
12
 
13
13
  protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
14
14
  const provider = await this.ensureConnected();