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
@@ -1,8 +1,9 @@
1
1
  import { createHmac } from "crypto";
2
2
  import { Telemetry } from "../../src/telemetry/telemetry.js";
3
- import { Session } from "../../src/session.js";
4
- import { config } from "../../src/config.js";
3
+ import { Session } from "../../src/common/session.js";
4
+ import { config } from "../../src/common/config.js";
5
5
  import nodeMachineId from "node-machine-id";
6
+ import { describe, expect, it } from "vitest";
6
7
 
7
8
  describe("Telemetry", () => {
8
9
  it("should resolve the actual machine ID", async () => {
@@ -20,7 +21,7 @@ describe("Telemetry", () => {
20
21
  expect(telemetry.getCommonProperties().device_id).toBe(undefined);
21
22
  expect(telemetry["isBufferingEvents"]).toBe(true);
22
23
 
23
- await telemetry.deviceIdPromise;
24
+ await telemetry.setupPromise;
24
25
 
25
26
  expect(telemetry.getCommonProperties().device_id).toBe(actualHashedId);
26
27
  expect(telemetry["isBufferingEvents"]).toBe(false);
@@ -1,6 +1,8 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { describeWithAtlas, withProject } from "./atlasHelpers.js";
3
3
  import { expectDefined } from "../../helpers.js";
4
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
+ import { ensureCurrentIpInAccessList } from "../../../../src/common/atlas/accessListUtils.js";
4
6
 
5
7
  function generateRandomIp() {
6
8
  const randomIp: number[] = [192];
@@ -65,7 +67,7 @@ describeWithAtlas("ip access lists", (integration) => {
65
67
  currentIpAddress: true,
66
68
  },
67
69
  })) as CallToolResult;
68
- expect(response.content).toBeArray();
70
+ expect(response.content).toBeInstanceOf(Array);
69
71
  expect(response.content).toHaveLength(1);
70
72
  expect(response.content[0]?.text).toContain("IP/CIDR ranges added to access list");
71
73
  });
@@ -87,12 +89,30 @@ describeWithAtlas("ip access lists", (integration) => {
87
89
  const response = (await integration
88
90
  .mcpClient()
89
91
  .callTool({ name: "atlas-inspect-access-list", arguments: { projectId } })) as CallToolResult;
90
- expect(response.content).toBeArray();
92
+ expect(response.content).toBeInstanceOf(Array);
91
93
  expect(response.content).toHaveLength(1);
92
94
  for (const value of values) {
93
95
  expect(response.content[0]?.text).toContain(value);
94
96
  }
95
97
  });
96
98
  });
99
+
100
+ describe("ensureCurrentIpInAccessList helper", () => {
101
+ it("should add the current IP to the access list and be idempotent", async () => {
102
+ const apiClient = integration.mcpServer().session.apiClient;
103
+ const projectId = getProjectId();
104
+ const ipInfo = await apiClient.getIpInfo();
105
+ // First call should add the IP
106
+ await expect(ensureCurrentIpInAccessList(apiClient, projectId)).resolves.not.toThrow();
107
+ // Second call should be a no-op (idempotent)
108
+ await expect(ensureCurrentIpInAccessList(apiClient, projectId)).resolves.not.toThrow();
109
+ // Check that the IP is present in the access list
110
+ const accessList = await apiClient.listProjectIpAccessLists({
111
+ params: { path: { groupId: projectId } },
112
+ });
113
+ const found = accessList.results?.some((entry) => entry.ipAddress === ipInfo.currentIpv4Address);
114
+ expect(found).toBe(true);
115
+ });
116
+ });
97
117
  });
98
118
  });
@@ -1,6 +1,7 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { expectDefined } from "../../helpers.js";
3
3
  import { parseTable, describeWithAtlas, withProject } from "./atlasHelpers.js";
4
+ import { describe, expect, it } from "vitest";
4
5
 
5
6
  describeWithAtlas("alerts", (integration) => {
6
7
  describe("atlas-list-alerts", () => {
@@ -20,11 +21,11 @@ describeWithAtlas("alerts", (integration) => {
20
21
  arguments: { projectId: getProjectId() },
21
22
  })) as CallToolResult;
22
23
 
23
- expect(response.content).toBeArray();
24
+ expect(response.content).toBeInstanceOf(Array);
24
25
  expect(response.content).toHaveLength(1);
25
26
 
26
27
  const data = parseTable(response.content[0]?.text as string);
27
- expect(data).toBeArray();
28
+ expect(data).toBeInstanceOf(Array);
28
29
 
29
30
  // Since we can't guarantee alerts will exist, we just verify the table structure
30
31
  if (data.length > 0) {
@@ -2,6 +2,7 @@ import { ObjectId } from "mongodb";
2
2
  import { Group } from "../../../../src/common/atlas/openapi.js";
3
3
  import { ApiClient } from "../../../../src/common/atlas/apiClient.js";
4
4
  import { setupIntegrationTest, IntegrationTest, defaultTestConfig } from "../../helpers.js";
5
+ import { afterAll, beforeAll, describe } from "vitest";
5
6
 
6
7
  export type IntegrationTestFunction = (integration: IntegrationTest) => void;
7
8
 
@@ -19,8 +20,10 @@ export function describeWithAtlas(name: string, fn: IntegrationTestFunction) {
19
20
  };
20
21
 
21
22
  if (!process.env.MDB_MCP_API_CLIENT_ID?.length || !process.env.MDB_MCP_API_CLIENT_SECRET?.length) {
23
+ // eslint-disable-next-line vitest/valid-describe-callback
22
24
  return describe.skip("atlas", testDefinition);
23
25
  }
26
+ // eslint-disable-next-line vitest/no-identical-title, vitest/valid-describe-callback
24
27
  return describe("atlas", testDefinition);
25
28
  }
26
29
 
@@ -1,7 +1,9 @@
1
- import { Session } from "../../../../src/session.js";
2
- import { expectDefined } from "../../helpers.js";
1
+ import { Session } from "../../../../src/common/session.js";
2
+ import { expectDefined, getResponseElements } from "../../helpers.js";
3
3
  import { describeWithAtlas, withProject, randomId } from "./atlasHelpers.js";
4
+ import { ClusterDescription20240805 } from "../../../../src/common/atlas/openapi.js";
4
5
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
6
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
7
 
6
8
  function sleep(ms: number) {
7
9
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -33,7 +35,12 @@ async function deleteAndWaitCluster(session: Session, projectId: string, cluster
33
35
  }
34
36
  }
35
37
 
36
- async function waitClusterState(session: Session, projectId: string, clusterName: string, state: string) {
38
+ async function waitCluster(
39
+ session: Session,
40
+ projectId: string,
41
+ clusterName: string,
42
+ check: (cluster: ClusterDescription20240805) => boolean | Promise<boolean>
43
+ ) {
37
44
  while (true) {
38
45
  const cluster = await session.apiClient.getCluster({
39
46
  params: {
@@ -43,7 +50,7 @@ async function waitClusterState(session: Session, projectId: string, clusterName
43
50
  },
44
51
  },
45
52
  });
46
- if (cluster?.stateName === state) {
53
+ if (await check(cluster)) {
47
54
  return;
48
55
  }
49
56
  await sleep(1000);
@@ -75,8 +82,10 @@ describeWithAtlas("clusters", (integration) => {
75
82
  expect(createFreeCluster.inputSchema.properties).toHaveProperty("region");
76
83
  });
77
84
 
78
- it("should create a free cluster", async () => {
85
+ it("should create a free cluster and add current IP to access list", async () => {
79
86
  const projectId = getProjectId();
87
+ const session = integration.mcpServer().session;
88
+ const ipInfo = await session.apiClient.getIpInfo();
80
89
 
81
90
  const response = (await integration.mcpClient().callTool({
82
91
  name: "atlas-create-free-cluster",
@@ -86,9 +95,16 @@ describeWithAtlas("clusters", (integration) => {
86
95
  region: "US_EAST_1",
87
96
  },
88
97
  })) as CallToolResult;
89
- expect(response.content).toBeArray();
98
+ expect(response.content).toBeInstanceOf(Array);
90
99
  expect(response.content).toHaveLength(2);
91
100
  expect(response.content[0]?.text).toContain("has been created");
101
+
102
+ // Check that the current IP is present in the access list
103
+ const accessList = await session.apiClient.listProjectIpAccessLists({
104
+ params: { path: { groupId: projectId } },
105
+ });
106
+ const found = accessList.results?.some((entry) => entry.ipAddress === ipInfo.currentIpv4Address);
107
+ expect(found).toBe(true);
92
108
  });
93
109
  });
94
110
 
@@ -111,7 +127,7 @@ describeWithAtlas("clusters", (integration) => {
111
127
  name: "atlas-inspect-cluster",
112
128
  arguments: { projectId, clusterName: clusterName },
113
129
  })) as CallToolResult;
114
- expect(response.content).toBeArray();
130
+ expect(response.content).toBeInstanceOf(Array);
115
131
  expect(response.content).toHaveLength(1);
116
132
  expect(response.content[0]?.text).toContain(`${clusterName} | `);
117
133
  });
@@ -133,7 +149,7 @@ describeWithAtlas("clusters", (integration) => {
133
149
  const response = (await integration
134
150
  .mcpClient()
135
151
  .callTool({ name: "atlas-list-clusters", arguments: { projectId } })) as CallToolResult;
136
- expect(response.content).toBeArray();
152
+ expect(response.content).toBeInstanceOf(Array);
137
153
  expect(response.content).toHaveLength(2);
138
154
  expect(response.content[1]?.text).toContain(`${clusterName} | `);
139
155
  });
@@ -142,7 +158,12 @@ describeWithAtlas("clusters", (integration) => {
142
158
  describe("atlas-connect-cluster", () => {
143
159
  beforeAll(async () => {
144
160
  const projectId = getProjectId();
145
- await waitClusterState(integration.mcpServer().session, projectId, clusterName, "IDLE");
161
+ await waitCluster(integration.mcpServer().session, projectId, clusterName, (cluster) => {
162
+ return (
163
+ cluster.stateName === "IDLE" &&
164
+ (cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard) !== undefined
165
+ );
166
+ });
146
167
  await integration.mcpServer().session.apiClient.createProjectIpAccessList({
147
168
  params: {
148
169
  path: {
@@ -172,13 +193,44 @@ describeWithAtlas("clusters", (integration) => {
172
193
  it("connects to cluster", async () => {
173
194
  const projectId = getProjectId();
174
195
 
175
- const response = (await integration.mcpClient().callTool({
176
- name: "atlas-connect-cluster",
177
- arguments: { projectId, clusterName },
178
- })) as CallToolResult;
179
- expect(response.content).toBeArray();
180
- expect(response.content).toHaveLength(1);
181
- expect(response.content[0]?.text).toContain(`Connected to cluster "${clusterName}"`);
196
+ for (let i = 0; i < 10; i++) {
197
+ const response = (await integration.mcpClient().callTool({
198
+ name: "atlas-connect-cluster",
199
+ arguments: { projectId, clusterName },
200
+ })) as CallToolResult;
201
+ expect(response.content).toBeInstanceOf(Array);
202
+ expect(response.content.length).toBeGreaterThanOrEqual(1);
203
+ expect(response.content[0]?.type).toEqual("text");
204
+ const c = response.content[0] as { text: string };
205
+ if (
206
+ c.text.includes("Cluster is already connected.") ||
207
+ c.text.includes(`Connected to cluster "${clusterName}"`)
208
+ ) {
209
+ break; // success
210
+ } else {
211
+ expect(response.content[0]?.text).toContain(
212
+ `Attempting to connect to cluster "${clusterName}"...`
213
+ );
214
+ }
215
+ await sleep(500);
216
+ }
217
+ });
218
+
219
+ describe("when not connected", () => {
220
+ it("prompts for atlas-connect-cluster when querying mongodb", async () => {
221
+ const response = await integration.mcpClient().callTool({
222
+ name: "find",
223
+ arguments: { database: "some-db", collection: "some-collection" },
224
+ });
225
+ const elements = getResponseElements(response.content);
226
+ expect(elements).toHaveLength(2);
227
+ expect(elements[0]?.text).toContain(
228
+ "You need to connect to a MongoDB instance before you can access its data."
229
+ );
230
+ expect(elements[1]?.text).toContain(
231
+ 'Please use one of the following tools: "atlas-connect-cluster", "connect" to connect to a MongoDB instance'
232
+ );
233
+ });
182
234
  });
183
235
  });
184
236
  });
@@ -2,6 +2,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { describeWithAtlas, withProject, randomId } from "./atlasHelpers.js";
3
3
  import { expectDefined, getResponseElements } from "../../helpers.js";
4
4
  import { ApiClientError } from "../../../../src/common/atlas/apiClientError.js";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
6
 
6
7
  describeWithAtlas("db users", (integration) => {
7
8
  withProject(integration, ({ getProjectId }) => {
@@ -78,6 +79,18 @@ describeWithAtlas("db users", (integration) => {
78
79
  expect(elements[0]?.text).toContain(userName);
79
80
  expect(elements[0]?.text).toContain("with password: `");
80
81
  });
82
+
83
+ it("should add current IP to access list when creating a database user", async () => {
84
+ const projectId = getProjectId();
85
+ const session = integration.mcpServer().session;
86
+ const ipInfo = await session.apiClient.getIpInfo();
87
+ await createUserWithMCP();
88
+ const accessList = await session.apiClient.listProjectIpAccessLists({
89
+ params: { path: { groupId: projectId } },
90
+ });
91
+ const found = accessList.results?.some((entry) => entry.ipAddress === ipInfo.currentIpv4Address);
92
+ expect(found).toBe(true);
93
+ });
81
94
  });
82
95
  describe("atlas-list-db-users", () => {
83
96
  it("should have correct metadata", async () => {
@@ -96,7 +109,7 @@ describeWithAtlas("db users", (integration) => {
96
109
  const response = (await integration
97
110
  .mcpClient()
98
111
  .callTool({ name: "atlas-list-db-users", arguments: { projectId } })) as CallToolResult;
99
- expect(response.content).toBeArray();
112
+ expect(response.content).toBeInstanceOf(Array);
100
113
  expect(response.content).toHaveLength(1);
101
114
  expect(response.content[0]?.text).toContain(userName);
102
115
  });
@@ -1,6 +1,7 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { expectDefined } from "../../helpers.js";
3
3
  import { parseTable, describeWithAtlas } from "./atlasHelpers.js";
4
+ import { describe, expect, it } from "vitest";
4
5
 
5
6
  describeWithAtlas("orgs", (integration) => {
6
7
  describe("atlas-list-orgs", () => {
@@ -14,7 +15,7 @@ describeWithAtlas("orgs", (integration) => {
14
15
  const response = (await integration
15
16
  .mcpClient()
16
17
  .callTool({ name: "atlas-list-orgs", arguments: {} })) as CallToolResult;
17
- expect(response.content).toBeArray();
18
+ expect(response.content).toBeInstanceOf(Array);
18
19
  expect(response.content).toHaveLength(1);
19
20
  const data = parseTable(response.content[0]?.text as string);
20
21
  expect(data).toHaveLength(1);
@@ -2,6 +2,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { ObjectId } from "mongodb";
3
3
  import { parseTable, describeWithAtlas } from "./atlasHelpers.js";
4
4
  import { expectDefined } from "../../helpers.js";
5
+ import { afterAll, describe, expect, it } from "vitest";
5
6
 
6
7
  const randomId = new ObjectId().toString();
7
8
 
@@ -41,7 +42,7 @@ describeWithAtlas("projects", (integration) => {
41
42
  name: "atlas-create-project",
42
43
  arguments: { projectName: projName },
43
44
  })) as CallToolResult;
44
- expect(response.content).toBeArray();
45
+ expect(response.content).toBeInstanceOf(Array);
45
46
  expect(response.content).toHaveLength(1);
46
47
  expect(response.content[0]?.text).toContain(projName);
47
48
  });
@@ -60,11 +61,11 @@ describeWithAtlas("projects", (integration) => {
60
61
  const response = (await integration
61
62
  .mcpClient()
62
63
  .callTool({ name: "atlas-list-projects", arguments: {} })) as CallToolResult;
63
- expect(response.content).toBeArray();
64
+ expect(response.content).toBeInstanceOf(Array);
64
65
  expect(response.content).toHaveLength(1);
65
66
  expect(response.content[0]?.text).toContain(projName);
66
67
  const data = parseTable(response.content[0]?.text as string);
67
- expect(data).toBeArray();
68
+ expect(data).toBeInstanceOf(Array);
68
69
  expect(data.length).toBeGreaterThan(0);
69
70
  let found = false;
70
71
  for (const project of data) {
@@ -1,9 +1,16 @@
1
1
  import { describeWithMongoDB } from "../mongodbHelpers.js";
2
- import { getResponseContent, validateThrowsForInvalidArguments, validateToolMetadata } from "../../../helpers.js";
3
- import { config } from "../../../../../src/config.js";
2
+ import {
3
+ getResponseContent,
4
+ getResponseElements,
5
+ validateThrowsForInvalidArguments,
6
+ validateToolMetadata,
7
+ } from "../../../helpers.js";
8
+ import { config } from "../../../../../src/common/config.js";
9
+ import { defaultTestConfig, setupIntegrationTest } from "../../../helpers.js";
10
+ import { beforeEach, describe, expect, it } from "vitest";
4
11
 
5
12
  describeWithMongoDB(
6
- "switchConnection tool",
13
+ "SwitchConnection tool",
7
14
  (integration) => {
8
15
  beforeEach(() => {
9
16
  integration.mcpServer().userConfig.connectionString = integration.connectionString();
@@ -77,6 +84,7 @@ describeWithMongoDB(
77
84
  connectionString: mdbIntegration.connectionString(),
78
85
  })
79
86
  );
87
+
80
88
  describeWithMongoDB(
81
89
  "Connect tool",
82
90
  (integration) => {
@@ -126,3 +134,26 @@ describeWithMongoDB(
126
134
  },
127
135
  () => config
128
136
  );
137
+
138
+ describe("Connect tool when disabled", () => {
139
+ const integration = setupIntegrationTest(() => ({
140
+ ...defaultTestConfig,
141
+ disabledTools: ["connect"],
142
+ }));
143
+
144
+ it("is not suggested when querying MongoDB disconnected", async () => {
145
+ const response = await integration.mcpClient().callTool({
146
+ name: "find",
147
+ arguments: { database: "some-db", collection: "some-collection" },
148
+ });
149
+
150
+ const elements = getResponseElements(response);
151
+ expect(elements).toHaveLength(2);
152
+ expect(elements[0]?.text).toContain(
153
+ "You need to connect to a MongoDB instance before you can access its data."
154
+ );
155
+ expect(elements[1]?.text).toContain(
156
+ "There are no tools available to connect. Please update the configuration to include a connection string and restart the server."
157
+ );
158
+ });
159
+ });
@@ -7,6 +7,7 @@ import {
7
7
  validateThrowsForInvalidArguments,
8
8
  databaseCollectionInvalidArgs,
9
9
  } from "../../../helpers.js";
10
+ import { describe, expect, it } from "vitest";
10
11
 
11
12
  describeWithMongoDB("createCollection tool", (integration) => {
12
13
  validateToolMetadata(
@@ -8,6 +8,7 @@ import {
8
8
  expectDefined,
9
9
  } from "../../../helpers.js";
10
10
  import { IndexDirection } from "mongodb";
11
+ import { expect, it } from "vitest";
11
12
 
12
13
  describeWithMongoDB("createIndex tool", (integration) => {
13
14
  validateToolMetadata(integration, "create-index", "Create an index for a collection", [
@@ -7,6 +7,7 @@ import {
7
7
  validateThrowsForInvalidArguments,
8
8
  expectDefined,
9
9
  } from "../../../helpers.js";
10
+ import { expect, it } from "vitest";
10
11
 
11
12
  describeWithMongoDB("insertMany tool", (integration) => {
12
13
  validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
@@ -6,6 +6,7 @@ import {
6
6
  validateToolMetadata,
7
7
  validateThrowsForInvalidArguments,
8
8
  } from "../../../helpers.js";
9
+ import { describe, expect, it } from "vitest";
9
10
 
10
11
  describeWithMongoDB("deleteMany tool", (integration) => {
11
12
  validateToolMetadata(
@@ -1,5 +1,5 @@
1
1
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
-
2
+ import { expect, it } from "vitest";
3
3
  import {
4
4
  getResponseContent,
5
5
  databaseCollectionParameters,
@@ -1,4 +1,5 @@
1
1
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+ import { expect, it } from "vitest";
2
3
 
3
4
  import {
4
5
  getResponseContent,
@@ -11,6 +11,7 @@ import {
11
11
  import { Document } from "bson";
12
12
  import { OptionalId } from "mongodb";
13
13
  import { SimplifiedSchema } from "mongodb-schema";
14
+ import { describe, expect, it } from "vitest";
14
15
 
15
16
  describeWithMongoDB("collectionSchema tool", (integration) => {
16
17
  validateToolMetadata(
@@ -9,6 +9,7 @@ import {
9
9
  expectDefined,
10
10
  } from "../../../helpers.js";
11
11
  import * as crypto from "crypto";
12
+ import { describe, expect, it } from "vitest";
12
13
 
13
14
  describeWithMongoDB("collectionStorageSize tool", (integration) => {
14
15
  validateToolMetadata(
@@ -8,6 +8,7 @@ import {
8
8
  } from "../../../helpers.js";
9
9
  import * as crypto from "crypto";
10
10
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
11
+ import { describe, expect, it } from "vitest";
11
12
 
12
13
  describeWithMongoDB("dbStats tool", (integration) => {
13
14
  validateToolMetadata(
@@ -5,6 +5,7 @@ import {
5
5
  getResponseElements,
6
6
  } from "../../../helpers.js";
7
7
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
8
+ import { beforeEach, describe, expect, it } from "vitest";
8
9
 
9
10
  describeWithMongoDB("explain tool", (integration) => {
10
11
  validateToolMetadata(
@@ -8,6 +8,7 @@ import {
8
8
  databaseInvalidArgs,
9
9
  databaseParameters,
10
10
  } from "../../../helpers.js";
11
+ import { describe, expect, it } from "vitest";
11
12
 
12
13
  describeWithMongoDB("listCollections tool", (integration) => {
13
14
  validateToolMetadata(
@@ -1,5 +1,6 @@
1
1
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
2
  import { getResponseElements, getParameters, expectDefined } from "../../../helpers.js";
3
+ import { describe, expect, it } from "vitest";
3
4
 
4
5
  describeWithMongoDB("listDatabases tool", (integration) => {
5
6
  const defaultDatabases = ["admin", "config", "local"];
@@ -20,7 +21,7 @@ describeWithMongoDB("listDatabases tool", (integration) => {
20
21
  const response = await integration.mcpClient().callTool({ name: "list-databases", arguments: {} });
21
22
  const dbNames = getDbNames(response.content);
22
23
 
23
- expect(defaultDatabases).toIncludeAllMembers(dbNames);
24
+ expect(defaultDatabases).toStrictEqual(dbNames);
24
25
  });
25
26
  });
26
27
 
@@ -47,7 +48,7 @@ describeWithMongoDB("listDatabases tool", (integration) => {
47
48
  validate: (content) => {
48
49
  const dbNames = getDbNames(content);
49
50
 
50
- expect(defaultDatabases).toIncludeAllMembers(dbNames);
51
+ expect(defaultDatabases).toStrictEqual(dbNames);
51
52
  },
52
53
  };
53
54
  },
@@ -1,3 +1,4 @@
1
+ import { expect, it } from "vitest";
1
2
  import { validateToolMetadata, validateThrowsForInvalidArguments, getResponseElements } from "../../../helpers.js";
2
3
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
3
4
 
@@ -2,12 +2,38 @@ import { MongoCluster } from "mongodb-runner";
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import fs from "fs/promises";
5
- import { MongoClient, ObjectId } from "mongodb";
5
+ import { Document, MongoClient, ObjectId } from "mongodb";
6
6
  import { getResponseContent, IntegrationTest, setupIntegrationTest, defaultTestConfig } from "../../helpers.js";
7
- import { UserConfig } from "../../../../src/config.js";
7
+ import { UserConfig } from "../../../../src/common/config.js";
8
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
8
9
 
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
 
12
+ const testDataDumpPath = path.join(__dirname, "..", "..", "..", "accuracy", "test-data-dumps");
13
+
14
+ const testDataPaths = [
15
+ {
16
+ db: "comics",
17
+ collection: "books",
18
+ path: path.join(testDataDumpPath, "comics.books.json"),
19
+ },
20
+ {
21
+ db: "comics",
22
+ collection: "characters",
23
+ path: path.join(testDataDumpPath, "comics.characters.json"),
24
+ },
25
+ {
26
+ db: "mflix",
27
+ collection: "movies",
28
+ path: path.join(testDataDumpPath, "mflix.movies.json"),
29
+ },
30
+ {
31
+ db: "mflix",
32
+ collection: "shows",
33
+ path: path.join(testDataDumpPath, "mflix.shows.json"),
34
+ },
35
+ ];
36
+
11
37
  interface MongoDBIntegrationTest {
12
38
  mongoClient: () => MongoClient;
13
39
  connectionString: () => string;
@@ -169,3 +195,41 @@ export function validateAutoConnectBehavior(
169
195
  });
170
196
  });
171
197
  }
198
+
199
+ export function prepareTestData(integration: MongoDBIntegrationTest) {
200
+ const NON_TEST_DBS = ["admin", "config", "local"];
201
+ const testData: {
202
+ db: string;
203
+ collection: string;
204
+ data: Document[];
205
+ }[] = [];
206
+
207
+ beforeAll(async () => {
208
+ for (const { db, collection, path } of testDataPaths) {
209
+ testData.push({
210
+ db,
211
+ collection,
212
+ data: JSON.parse(await fs.readFile(path, "utf8")) as Document[],
213
+ });
214
+ }
215
+ });
216
+
217
+ return {
218
+ async populateTestData(this: void) {
219
+ const client = integration.mongoClient();
220
+ for (const { db, collection, data } of testData) {
221
+ await client.db(db).collection(collection).insertMany(data);
222
+ }
223
+ },
224
+ async cleanupTestDatabases(this: void, integration: MongoDBIntegrationTest) {
225
+ const client = integration.mongoClient();
226
+ const admin = client.db().admin();
227
+ const databases = await admin.listDatabases();
228
+ await Promise.all(
229
+ databases.databases
230
+ .filter(({ name }) => !NON_TEST_DBS.includes(name))
231
+ .map(({ name }) => client.db(name).dropDatabase())
232
+ );
233
+ },
234
+ };
235
+ }
@@ -4,6 +4,7 @@ import {
4
4
  validateThrowsForInvalidArguments,
5
5
  getResponseElements,
6
6
  } from "../../../helpers.js";
7
+ import { expect, it } from "vitest";
7
8
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
8
9
 
9
10
  describeWithMongoDB("aggregate tool", (integration) => {
@@ -20,7 +21,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
20
21
  validateThrowsForInvalidArguments(integration, "aggregate", [
21
22
  {},
22
23
  { database: "test", collection: "foo" },
23
- { database: test, pipeline: [] },
24
+ { database: "test", pipeline: [] },
24
25
  { database: "test", collection: "foo", pipeline: {} },
25
26
  { database: "test", collection: [], pipeline: [] },
26
27
  { database: 123, collection: "foo", pipeline: [] },
@@ -8,6 +8,7 @@ import {
8
8
  expectDefined,
9
9
  } from "../../../helpers.js";
10
10
  import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
11
+ import { expect, it } from "vitest";
11
12
 
12
13
  describeWithMongoDB("collectionIndexes tool", (integration) => {
13
14
  validateToolMetadata(
@@ -6,6 +6,7 @@ import {
6
6
  validateToolMetadata,
7
7
  validateThrowsForInvalidArguments,
8
8
  } from "../../../helpers.js";
9
+ import { beforeEach, describe, expect, it } from "vitest";
9
10
 
10
11
  describeWithMongoDB("count tool", (integration) => {
11
12
  validateToolMetadata(