mongodb-mcp-server 0.1.2 → 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 (270) hide show
  1. package/.github/pull_request_template.md +5 -0
  2. package/.github/workflows/accuracy-tests.yml +55 -0
  3. package/.github/workflows/check.yml +1 -1
  4. package/.github/workflows/code_health.yaml +4 -4
  5. package/.github/workflows/code_health_fork.yaml +0 -14
  6. package/.github/workflows/dependabot_pr.yaml +26 -0
  7. package/.github/workflows/docker.yaml +1 -1
  8. package/.github/workflows/jira-issue.yml +72 -0
  9. package/.smithery/smithery.yaml +10 -0
  10. package/.vscode/extensions.json +1 -1
  11. package/.vscode/launch.json +11 -1
  12. package/.vscode/settings.json +1 -11
  13. package/Dockerfile +1 -0
  14. package/README.md +132 -31
  15. package/dist/common/atlas/accessListUtils.js +36 -0
  16. package/dist/common/atlas/accessListUtils.js.map +1 -0
  17. package/dist/common/atlas/apiClient.js +25 -6
  18. package/dist/common/atlas/apiClient.js.map +1 -1
  19. package/dist/common/atlas/cluster.js +1 -1
  20. package/dist/common/atlas/cluster.js.map +1 -1
  21. package/dist/{config.js → common/config.js} +8 -1
  22. package/dist/common/config.js.map +1 -0
  23. package/dist/{errors.js → common/errors.js} +1 -0
  24. package/dist/common/errors.js.map +1 -0
  25. package/dist/{logger.js → common/logger.js} +20 -19
  26. package/dist/common/logger.js.map +1 -0
  27. package/dist/common/managedTimeout.js +20 -0
  28. package/dist/common/managedTimeout.js.map +1 -0
  29. package/dist/common/packageInfo.js.map +1 -0
  30. package/dist/{session.js → common/session.js} +20 -21
  31. package/dist/common/session.js.map +1 -0
  32. package/dist/common/sessionStore.js +73 -0
  33. package/dist/common/sessionStore.js.map +1 -0
  34. package/dist/helpers/container.js +28 -0
  35. package/dist/helpers/container.js.map +1 -0
  36. package/dist/helpers/generatePassword.js.map +1 -0
  37. package/dist/helpers/indexCheck.js +63 -0
  38. package/dist/helpers/indexCheck.js.map +1 -0
  39. package/dist/index.js +30 -37
  40. package/dist/index.js.map +1 -1
  41. package/dist/server.js +44 -7
  42. package/dist/server.js.map +1 -1
  43. package/dist/telemetry/constants.js +1 -1
  44. package/dist/telemetry/constants.js.map +1 -1
  45. package/dist/telemetry/telemetry.js +86 -116
  46. package/dist/telemetry/telemetry.js.map +1 -1
  47. package/dist/tools/atlas/atlasTool.js +3 -3
  48. package/dist/tools/atlas/atlasTool.js.map +1 -1
  49. package/dist/tools/atlas/connect/connectCluster.js +198 -0
  50. package/dist/tools/atlas/connect/connectCluster.js.map +1 -0
  51. package/dist/tools/atlas/create/createAccessList.js +9 -10
  52. package/dist/tools/atlas/create/createAccessList.js.map +1 -1
  53. package/dist/tools/atlas/create/createDBUser.js +3 -1
  54. package/dist/tools/atlas/create/createDBUser.js.map +1 -1
  55. package/dist/tools/atlas/create/createFreeCluster.js +2 -0
  56. package/dist/tools/atlas/create/createFreeCluster.js.map +1 -1
  57. package/dist/tools/atlas/create/createProject.js.map +1 -1
  58. package/dist/tools/atlas/read/inspectAccessList.js.map +1 -1
  59. package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
  60. package/dist/tools/atlas/read/listAlerts.js.map +1 -1
  61. package/dist/tools/atlas/read/listClusters.js.map +1 -1
  62. package/dist/tools/atlas/read/listDBUsers.js.map +1 -1
  63. package/dist/tools/atlas/read/listOrgs.js.map +1 -1
  64. package/dist/tools/atlas/read/listProjects.js.map +1 -1
  65. package/dist/tools/atlas/tools.js +1 -1
  66. package/dist/tools/atlas/tools.js.map +1 -1
  67. package/dist/tools/mongodb/{metadata → connect}/connect.js +7 -4
  68. package/dist/tools/mongodb/connect/connect.js.map +1 -0
  69. package/dist/tools/mongodb/create/createCollection.js.map +1 -1
  70. package/dist/tools/mongodb/create/createIndex.js +1 -1
  71. package/dist/tools/mongodb/create/createIndex.js.map +1 -1
  72. package/dist/tools/mongodb/create/insertMany.js +1 -1
  73. package/dist/tools/mongodb/create/insertMany.js.map +1 -1
  74. package/dist/tools/mongodb/delete/deleteMany.js +20 -1
  75. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
  76. package/dist/tools/mongodb/delete/dropCollection.js.map +1 -1
  77. package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -1
  78. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
  79. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
  80. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
  81. package/dist/tools/mongodb/metadata/explain.js +2 -2
  82. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  83. package/dist/tools/mongodb/metadata/listCollections.js.map +1 -1
  84. package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -1
  85. package/dist/tools/mongodb/metadata/logs.js.map +1 -1
  86. package/dist/tools/mongodb/mongodbTool.js +47 -10
  87. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  88. package/dist/tools/mongodb/read/aggregate.js +10 -1
  89. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  90. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
  91. package/dist/tools/mongodb/read/count.js +15 -1
  92. package/dist/tools/mongodb/read/count.js.map +1 -1
  93. package/dist/tools/mongodb/read/find.js +14 -4
  94. package/dist/tools/mongodb/read/find.js.map +1 -1
  95. package/dist/tools/mongodb/tools.js +1 -1
  96. package/dist/tools/mongodb/tools.js.map +1 -1
  97. package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
  98. package/dist/tools/mongodb/update/updateMany.js +24 -2
  99. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  100. package/dist/tools/tool.js +12 -9
  101. package/dist/tools/tool.js.map +1 -1
  102. package/dist/transports/base.js +26 -0
  103. package/dist/transports/base.js.map +1 -0
  104. package/dist/{helpers/EJsonTransport.js → transports/stdio.js} +24 -2
  105. package/dist/transports/stdio.js.map +1 -0
  106. package/dist/transports/streamableHttp.js +140 -0
  107. package/dist/transports/streamableHttp.js.map +1 -0
  108. package/eslint.config.js +13 -4
  109. package/package.json +43 -33
  110. package/resources/test-summary-template.html +415 -0
  111. package/scripts/accuracy/generateTestSummary.ts +335 -0
  112. package/scripts/accuracy/runAccuracyTests.sh +45 -0
  113. package/scripts/accuracy/updateAccuracyRunStatus.ts +21 -0
  114. package/src/common/atlas/accessListUtils.ts +54 -0
  115. package/src/common/atlas/apiClient.ts +25 -6
  116. package/src/common/atlas/cluster.ts +1 -1
  117. package/src/{config.ts → common/config.ts} +16 -2
  118. package/src/{errors.ts → common/errors.ts} +1 -0
  119. package/src/{logger.ts → common/logger.ts} +21 -24
  120. package/src/common/managedTimeout.ts +27 -0
  121. package/src/{session.ts → common/session.ts} +24 -26
  122. package/src/common/sessionStore.ts +111 -0
  123. package/src/helpers/container.ts +35 -0
  124. package/src/helpers/indexCheck.ts +83 -0
  125. package/src/index.ts +30 -40
  126. package/src/server.ts +55 -11
  127. package/src/telemetry/constants.ts +1 -1
  128. package/src/telemetry/telemetry.ts +109 -153
  129. package/src/telemetry/types.ts +2 -1
  130. package/src/tools/atlas/atlasTool.ts +4 -4
  131. package/src/tools/atlas/connect/connectCluster.ts +259 -0
  132. package/src/tools/atlas/create/createAccessList.ts +15 -13
  133. package/src/tools/atlas/create/createDBUser.ts +5 -3
  134. package/src/tools/atlas/create/createFreeCluster.ts +4 -2
  135. package/src/tools/atlas/create/createProject.ts +2 -2
  136. package/src/tools/atlas/read/inspectAccessList.ts +2 -2
  137. package/src/tools/atlas/read/inspectCluster.ts +2 -2
  138. package/src/tools/atlas/read/listAlerts.ts +2 -2
  139. package/src/tools/atlas/read/listClusters.ts +2 -2
  140. package/src/tools/atlas/read/listDBUsers.ts +2 -2
  141. package/src/tools/atlas/read/listOrgs.ts +2 -2
  142. package/src/tools/atlas/read/listProjects.ts +2 -2
  143. package/src/tools/atlas/tools.ts +1 -1
  144. package/src/tools/mongodb/{metadata → connect}/connect.ts +12 -9
  145. package/src/tools/mongodb/create/createCollection.ts +2 -2
  146. package/src/tools/mongodb/create/createIndex.ts +3 -3
  147. package/src/tools/mongodb/create/insertMany.ts +3 -3
  148. package/src/tools/mongodb/delete/deleteMany.ts +24 -3
  149. package/src/tools/mongodb/delete/dropCollection.ts +2 -2
  150. package/src/tools/mongodb/delete/dropDatabase.ts +2 -2
  151. package/src/tools/mongodb/metadata/collectionSchema.ts +2 -2
  152. package/src/tools/mongodb/metadata/collectionStorageSize.ts +2 -2
  153. package/src/tools/mongodb/metadata/dbStats.ts +2 -2
  154. package/src/tools/mongodb/metadata/explain.ts +4 -4
  155. package/src/tools/mongodb/metadata/listCollections.ts +2 -2
  156. package/src/tools/mongodb/metadata/listDatabases.ts +2 -2
  157. package/src/tools/mongodb/metadata/logs.ts +2 -2
  158. package/src/tools/mongodb/mongodbTool.ts +60 -14
  159. package/src/tools/mongodb/read/aggregate.ts +14 -3
  160. package/src/tools/mongodb/read/collectionIndexes.ts +2 -2
  161. package/src/tools/mongodb/read/count.ts +19 -3
  162. package/src/tools/mongodb/read/find.ts +20 -6
  163. package/src/tools/mongodb/tools.ts +1 -1
  164. package/src/tools/mongodb/update/renameCollection.ts +2 -2
  165. package/src/tools/mongodb/update/updateMany.ts +28 -4
  166. package/src/tools/tool.ts +23 -18
  167. package/src/transports/base.ts +34 -0
  168. package/src/{helpers/EJsonTransport.ts → transports/stdio.ts} +30 -1
  169. package/src/transports/streamableHttp.ts +178 -0
  170. package/tests/accuracy/aggregate.test.ts +27 -0
  171. package/tests/accuracy/collectionIndexes.test.ts +40 -0
  172. package/tests/accuracy/collectionSchema.test.ts +28 -0
  173. package/tests/accuracy/collectionStorageSize.test.ts +41 -0
  174. package/tests/accuracy/count.test.ts +44 -0
  175. package/tests/accuracy/createCollection.test.ts +46 -0
  176. package/tests/accuracy/createIndex.test.ts +37 -0
  177. package/tests/accuracy/dbStats.test.ts +15 -0
  178. package/tests/accuracy/deleteMany.test.ts +44 -0
  179. package/tests/accuracy/dropCollection.test.ts +74 -0
  180. package/tests/accuracy/dropDatabase.test.ts +41 -0
  181. package/tests/accuracy/explain.test.ts +73 -0
  182. package/tests/accuracy/find.test.ts +114 -0
  183. package/tests/accuracy/insertMany.test.ts +48 -0
  184. package/tests/accuracy/listCollections.test.ts +60 -0
  185. package/tests/accuracy/listDatabases.test.ts +31 -0
  186. package/tests/accuracy/logs.test.ts +28 -0
  187. package/tests/accuracy/renameCollection.test.ts +31 -0
  188. package/tests/accuracy/sdk/accuracyResultStorage/diskStorage.ts +189 -0
  189. package/tests/accuracy/sdk/accuracyResultStorage/getAccuracyResultStorage.ts +11 -0
  190. package/tests/accuracy/sdk/accuracyResultStorage/mongodbStorage.ts +151 -0
  191. package/tests/accuracy/sdk/accuracyResultStorage/resultStorage.ts +117 -0
  192. package/tests/accuracy/sdk/accuracyScorer.ts +93 -0
  193. package/tests/accuracy/sdk/accuracyTestingClient.ts +94 -0
  194. package/tests/accuracy/sdk/agent.ts +56 -0
  195. package/tests/accuracy/sdk/constants.ts +26 -0
  196. package/tests/accuracy/sdk/describeAccuracyTests.ts +126 -0
  197. package/tests/accuracy/sdk/gitInfo.ts +7 -0
  198. package/tests/accuracy/sdk/matcher.ts +193 -0
  199. package/tests/accuracy/sdk/models.ts +95 -0
  200. package/tests/accuracy/test-data-dumps/comics.books.json +417 -0
  201. package/tests/accuracy/test-data-dumps/comics.characters.json +402 -0
  202. package/tests/accuracy/test-data-dumps/mflix.movies.json +496 -0
  203. package/tests/accuracy/test-data-dumps/mflix.shows.json +572 -0
  204. package/tests/accuracy/updateMany.test.ts +42 -0
  205. package/tests/integration/helpers.ts +9 -9
  206. package/tests/integration/indexCheck.test.ts +464 -0
  207. package/tests/integration/server.test.ts +6 -4
  208. package/tests/integration/telemetry.test.ts +29 -0
  209. package/tests/integration/tools/atlas/accessLists.test.ts +22 -2
  210. package/tests/integration/tools/atlas/alerts.test.ts +3 -2
  211. package/tests/integration/tools/atlas/atlasHelpers.ts +3 -0
  212. package/tests/integration/tools/atlas/clusters.test.ts +68 -16
  213. package/tests/integration/tools/atlas/dbUsers.test.ts +14 -1
  214. package/tests/integration/tools/atlas/orgs.test.ts +2 -1
  215. package/tests/integration/tools/atlas/projects.test.ts +4 -3
  216. package/tests/integration/tools/mongodb/{metadata → connect}/connect.test.ts +34 -3
  217. package/tests/integration/tools/mongodb/create/createCollection.test.ts +1 -0
  218. package/tests/integration/tools/mongodb/create/createIndex.test.ts +1 -0
  219. package/tests/integration/tools/mongodb/create/insertMany.test.ts +1 -0
  220. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +1 -0
  221. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +1 -1
  222. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +1 -0
  223. package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +1 -0
  224. package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +1 -0
  225. package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +1 -0
  226. package/tests/integration/tools/mongodb/metadata/explain.test.ts +1 -0
  227. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +1 -0
  228. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +3 -2
  229. package/tests/integration/tools/mongodb/metadata/logs.test.ts +1 -0
  230. package/tests/integration/tools/mongodb/mongodbHelpers.ts +67 -2
  231. package/tests/integration/tools/mongodb/read/aggregate.test.ts +2 -1
  232. package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +1 -0
  233. package/tests/integration/tools/mongodb/read/count.test.ts +1 -0
  234. package/tests/integration/tools/mongodb/read/find.test.ts +2 -1
  235. package/tests/integration/tools/mongodb/update/renameCollection.test.ts +1 -0
  236. package/tests/integration/tools/mongodb/update/updateMany.test.ts +1 -0
  237. package/tests/integration/transports/stdio.test.ts +40 -0
  238. package/tests/integration/transports/streamableHttp.test.ts +56 -0
  239. package/tests/matchers/toIncludeSameMembers.test.ts +59 -0
  240. package/tests/matchers/toIncludeSameMembers.ts +12 -0
  241. package/tests/setup.ts +7 -0
  242. package/tests/unit/accessListUtils.test.ts +39 -0
  243. package/tests/unit/accuracyScorer.test.ts +390 -0
  244. package/tests/unit/{apiClient.test.ts → common/apiClient.test.ts} +15 -15
  245. package/tests/unit/common/managedTimeout.test.ts +67 -0
  246. package/tests/unit/{session.test.ts → common/session.test.ts} +7 -12
  247. package/tests/unit/helpers/indexCheck.test.ts +150 -0
  248. package/tests/unit/telemetry.test.ts +99 -137
  249. package/tests/unit/{EJsonTransport.test.ts → transports/stdio.test.ts} +4 -4
  250. package/tests/vitest.d.ts +11 -0
  251. package/tsconfig.json +0 -1
  252. package/{tsconfig.jest.json → tsconfig.test.json} +1 -2
  253. package/vitest.config.ts +41 -0
  254. package/dist/common/atlas/generatePassword.js.map +0 -1
  255. package/dist/config.js.map +0 -1
  256. package/dist/errors.js.map +0 -1
  257. package/dist/helpers/EJsonTransport.js.map +0 -1
  258. package/dist/helpers/packageInfo.js.map +0 -1
  259. package/dist/logger.js.map +0 -1
  260. package/dist/session.js.map +0 -1
  261. package/dist/tools/atlas/metadata/connectCluster.js +0 -100
  262. package/dist/tools/atlas/metadata/connectCluster.js.map +0 -1
  263. package/dist/tools/mongodb/metadata/connect.js.map +0 -1
  264. package/global.d.ts +0 -1
  265. package/jest.config.cjs +0 -22
  266. package/src/tools/atlas/metadata/connectCluster.ts +0 -121
  267. /package/dist/{helpers → common}/packageInfo.js +0 -0
  268. /package/dist/{common/atlas → helpers}/generatePassword.js +0 -0
  269. /package/src/{helpers → common}/packageInfo.ts +0 -0
  270. /package/src/{common/atlas → helpers}/generatePassword.ts +0 -0
@@ -0,0 +1,464 @@
1
+ import { defaultTestConfig, getResponseContent } from "./helpers.js";
2
+ import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js";
3
+ import { beforeEach, describe, expect, it } from "vitest";
4
+
5
+ describe("IndexCheck integration tests", () => {
6
+ describe("with indexCheck enabled", () => {
7
+ describeWithMongoDB(
8
+ "indexCheck functionality",
9
+ (integration) => {
10
+ beforeEach(async () => {
11
+ await integration.connectMcpClient();
12
+ });
13
+
14
+ describe("find operations", () => {
15
+ beforeEach(async () => {
16
+ // Insert test data for find operations
17
+ await integration
18
+ .mongoClient()
19
+ .db(integration.randomDbName())
20
+ .collection("find-test-collection")
21
+ .insertMany([
22
+ { name: "document1", value: 1, category: "A" },
23
+ { name: "document2", value: 2, category: "B" },
24
+ { name: "document3", value: 3, category: "A" },
25
+ ]);
26
+ });
27
+
28
+ it("should reject queries that perform collection scans", async () => {
29
+ const response = await integration.mcpClient().callTool({
30
+ name: "find",
31
+ arguments: {
32
+ database: integration.randomDbName(),
33
+ collection: "find-test-collection",
34
+ filter: { category: "A" }, // No index on category field
35
+ },
36
+ });
37
+
38
+ const content = getResponseContent(response.content);
39
+ expect(content).toContain("Index check failed");
40
+ expect(content).toContain("collection scan (COLLSCAN)");
41
+ expect(content).toContain("MDB_MCP_INDEX_CHECK");
42
+ expect(response.isError).toBe(true);
43
+ });
44
+
45
+ it("should allow queries that use indexes", async () => {
46
+ // Create an index on the category field
47
+ await integration
48
+ .mongoClient()
49
+ .db(integration.randomDbName())
50
+ .collection("find-test-collection")
51
+ .createIndex({ category: 1 });
52
+
53
+ const response = await integration.mcpClient().callTool({
54
+ name: "find",
55
+ arguments: {
56
+ database: integration.randomDbName(),
57
+ collection: "find-test-collection",
58
+ filter: { category: "A" }, // Now has index
59
+ },
60
+ });
61
+
62
+ expect(response.isError).toBeFalsy();
63
+ const content = getResponseContent(response.content);
64
+ expect(content).toContain("Found");
65
+ expect(content).toContain("documents");
66
+ });
67
+
68
+ it("should allow queries using _id (IDHACK)", async () => {
69
+ const docs = await integration
70
+ .mongoClient()
71
+ .db(integration.randomDbName())
72
+ .collection("find-test-collection")
73
+ .find({})
74
+ .toArray();
75
+
76
+ expect(docs.length).toBeGreaterThan(0);
77
+
78
+ const response = await integration.mcpClient().callTool({
79
+ name: "find",
80
+ arguments: {
81
+ database: integration.randomDbName(),
82
+ collection: "find-test-collection",
83
+ filter: { _id: docs[0]?._id }, // Uses _id index (IDHACK)
84
+ },
85
+ });
86
+
87
+ expect(response.isError).toBeFalsy();
88
+ const content = getResponseContent(response.content);
89
+ expect(content).toContain("Found 1 documents");
90
+ });
91
+ });
92
+
93
+ describe("count operations", () => {
94
+ beforeEach(async () => {
95
+ // Insert test data for count operations
96
+ await integration
97
+ .mongoClient()
98
+ .db(integration.randomDbName())
99
+ .collection("count-test-collection")
100
+ .insertMany([
101
+ { name: "document1", value: 1, category: "A" },
102
+ { name: "document2", value: 2, category: "B" },
103
+ { name: "document3", value: 3, category: "A" },
104
+ ]);
105
+ });
106
+
107
+ it("should reject count queries that perform collection scans", async () => {
108
+ const response = await integration.mcpClient().callTool({
109
+ name: "count",
110
+ arguments: {
111
+ database: integration.randomDbName(),
112
+ collection: "count-test-collection",
113
+ query: { value: { $gt: 1 } }, // No index on value field
114
+ },
115
+ });
116
+
117
+ const content = getResponseContent(response.content);
118
+ expect(content).toContain("Index check failed");
119
+ expect(content).toContain("count operation");
120
+ expect(response.isError).toBe(true);
121
+ });
122
+
123
+ it("should allow count queries with indexes", async () => {
124
+ // Create an index on the value field
125
+ await integration
126
+ .mongoClient()
127
+ .db(integration.randomDbName())
128
+ .collection("count-test-collection")
129
+ .createIndex({ value: 1 });
130
+
131
+ const response = await integration.mcpClient().callTool({
132
+ name: "count",
133
+ arguments: {
134
+ database: integration.randomDbName(),
135
+ collection: "count-test-collection",
136
+ query: { value: { $gt: 1 } }, // Now has index
137
+ },
138
+ });
139
+
140
+ expect(response.isError).toBeFalsy();
141
+ const content = getResponseContent(response.content);
142
+ expect(content).toContain("Found");
143
+ expect(content).toMatch(/\d+ documents/);
144
+ });
145
+ });
146
+
147
+ describe("aggregate operations", () => {
148
+ beforeEach(async () => {
149
+ // Insert test data for aggregate operations
150
+ await integration
151
+ .mongoClient()
152
+ .db(integration.randomDbName())
153
+ .collection("aggregate-test-collection")
154
+ .insertMany([
155
+ { name: "document1", value: 1, category: "A" },
156
+ { name: "document2", value: 2, category: "B" },
157
+ { name: "document3", value: 3, category: "A" },
158
+ ]);
159
+ });
160
+
161
+ it("should reject aggregation queries that perform collection scans", async () => {
162
+ const response = await integration.mcpClient().callTool({
163
+ name: "aggregate",
164
+ arguments: {
165
+ database: integration.randomDbName(),
166
+ collection: "aggregate-test-collection",
167
+ pipeline: [
168
+ { $match: { category: "A" } }, // No index on category
169
+ { $group: { _id: "$category", count: { $sum: 1 } } },
170
+ ],
171
+ },
172
+ });
173
+
174
+ const content = getResponseContent(response.content);
175
+ expect(content).toContain("Index check failed");
176
+ expect(content).toContain("aggregate operation");
177
+ expect(response.isError).toBe(true);
178
+ });
179
+
180
+ it("should allow aggregation queries with indexes", async () => {
181
+ // Create an index on the category field
182
+ await integration
183
+ .mongoClient()
184
+ .db(integration.randomDbName())
185
+ .collection("aggregate-test-collection")
186
+ .createIndex({ category: 1 });
187
+
188
+ const response = await integration.mcpClient().callTool({
189
+ name: "aggregate",
190
+ arguments: {
191
+ database: integration.randomDbName(),
192
+ collection: "aggregate-test-collection",
193
+ pipeline: [
194
+ { $match: { category: "A" } }, // Now has index
195
+ ],
196
+ },
197
+ });
198
+
199
+ expect(response.isError).toBeFalsy();
200
+ const content = getResponseContent(response.content);
201
+ expect(content).toContain("Found");
202
+ });
203
+ });
204
+
205
+ describe("updateMany operations", () => {
206
+ beforeEach(async () => {
207
+ // Insert test data for updateMany operations
208
+ await integration
209
+ .mongoClient()
210
+ .db(integration.randomDbName())
211
+ .collection("update-test-collection")
212
+ .insertMany([
213
+ { name: "document1", value: 1, category: "A" },
214
+ { name: "document2", value: 2, category: "B" },
215
+ { name: "document3", value: 3, category: "A" },
216
+ ]);
217
+ });
218
+
219
+ it("should reject updateMany queries that perform collection scans", async () => {
220
+ const response = await integration.mcpClient().callTool({
221
+ name: "update-many",
222
+ arguments: {
223
+ database: integration.randomDbName(),
224
+ collection: "update-test-collection",
225
+ filter: { category: "A" }, // No index on category
226
+ update: { $set: { updated: true } },
227
+ },
228
+ });
229
+
230
+ const content = getResponseContent(response.content);
231
+ expect(content).toContain("Index check failed");
232
+ expect(content).toContain("updateMany operation");
233
+ expect(response.isError).toBe(true);
234
+ });
235
+
236
+ it("should allow updateMany queries with indexes", async () => {
237
+ // Create an index on the category field
238
+ await integration
239
+ .mongoClient()
240
+ .db(integration.randomDbName())
241
+ .collection("update-test-collection")
242
+ .createIndex({ category: 1 });
243
+
244
+ const response = await integration.mcpClient().callTool({
245
+ name: "update-many",
246
+ arguments: {
247
+ database: integration.randomDbName(),
248
+ collection: "update-test-collection",
249
+ filter: { category: "A" }, // Now has index
250
+ update: { $set: { updated: true } },
251
+ },
252
+ });
253
+
254
+ expect(response.isError).toBeFalsy();
255
+ const content = getResponseContent(response.content);
256
+ expect(content).toContain("Matched");
257
+ expect(content).toContain("Modified");
258
+ });
259
+ });
260
+
261
+ describe("deleteMany operations", () => {
262
+ beforeEach(async () => {
263
+ // Insert test data for deleteMany operations
264
+ await integration
265
+ .mongoClient()
266
+ .db(integration.randomDbName())
267
+ .collection("delete-test-collection")
268
+ .insertMany([
269
+ { name: "document1", value: 1, category: "A" },
270
+ { name: "document2", value: 2, category: "B" },
271
+ { name: "document3", value: 3, category: "A" },
272
+ ]);
273
+ });
274
+
275
+ it("should reject deleteMany queries that perform collection scans", async () => {
276
+ const response = await integration.mcpClient().callTool({
277
+ name: "delete-many",
278
+ arguments: {
279
+ database: integration.randomDbName(),
280
+ collection: "delete-test-collection",
281
+ filter: { value: { $lt: 2 } }, // No index on value
282
+ },
283
+ });
284
+
285
+ const content = getResponseContent(response.content);
286
+ expect(content).toContain("Index check failed");
287
+ expect(content).toContain("deleteMany operation");
288
+ expect(response.isError).toBe(true);
289
+ });
290
+
291
+ it("should allow deleteMany queries with indexes", async () => {
292
+ // Create an index on the value field
293
+ await integration
294
+ .mongoClient()
295
+ .db(integration.randomDbName())
296
+ .collection("delete-test-collection")
297
+ .createIndex({ value: 1 });
298
+
299
+ const response = await integration.mcpClient().callTool({
300
+ name: "delete-many",
301
+ arguments: {
302
+ database: integration.randomDbName(),
303
+ collection: "delete-test-collection",
304
+ filter: { value: { $lt: 2 } }, // Now has index
305
+ },
306
+ });
307
+
308
+ expect(response.isError).toBeFalsy();
309
+ const content = getResponseContent(response.content);
310
+ expect(content).toContain("Deleted");
311
+ expect(content).toMatch(/`\d+` document\(s\)/);
312
+ });
313
+ });
314
+ },
315
+ () => ({
316
+ ...defaultTestConfig,
317
+ indexCheck: true, // Enable indexCheck
318
+ })
319
+ );
320
+ });
321
+
322
+ describe("with indexCheck disabled", () => {
323
+ describeWithMongoDB(
324
+ "indexCheck disabled functionality",
325
+ (integration) => {
326
+ beforeEach(async () => {
327
+ await integration.connectMcpClient();
328
+
329
+ // insert test data for disabled indexCheck tests
330
+ await integration
331
+ .mongoClient()
332
+ .db(integration.randomDbName())
333
+ .collection("disabled-test-collection")
334
+ .insertMany([
335
+ { name: "document1", value: 1, category: "A" },
336
+ { name: "document2", value: 2, category: "B" },
337
+ { name: "document3", value: 3, category: "A" },
338
+ ]);
339
+ });
340
+
341
+ it("should allow all queries regardless of index usage", async () => {
342
+ // Test find operation without index
343
+ const findResponse = await integration.mcpClient().callTool({
344
+ name: "find",
345
+ arguments: {
346
+ database: integration.randomDbName(),
347
+ collection: "disabled-test-collection",
348
+ filter: { category: "A" }, // No index, but should be allowed
349
+ },
350
+ });
351
+
352
+ expect(findResponse.isError).toBeFalsy();
353
+ const findContent = getResponseContent(findResponse.content);
354
+ expect(findContent).toContain("Found");
355
+ expect(findContent).not.toContain("Index check failed");
356
+ });
357
+
358
+ it("should allow count operations without indexes", async () => {
359
+ const response = await integration.mcpClient().callTool({
360
+ name: "count",
361
+ arguments: {
362
+ database: integration.randomDbName(),
363
+ collection: "disabled-test-collection",
364
+ query: { value: { $gt: 1 } }, // No index, but should be allowed
365
+ },
366
+ });
367
+
368
+ expect(response.isError).toBeFalsy();
369
+ const content = getResponseContent(response.content);
370
+ expect(content).toContain("Found");
371
+ expect(content).not.toContain("Index check failed");
372
+ });
373
+
374
+ it("should allow aggregate operations without indexes", async () => {
375
+ const response = await integration.mcpClient().callTool({
376
+ name: "aggregate",
377
+ arguments: {
378
+ database: integration.randomDbName(),
379
+ collection: "disabled-test-collection",
380
+ pipeline: [
381
+ { $match: { category: "A" } }, // No index, but should be allowed
382
+ { $group: { _id: "$category", count: { $sum: 1 } } },
383
+ ],
384
+ },
385
+ });
386
+
387
+ expect(response.isError).toBeFalsy();
388
+ const content = getResponseContent(response.content);
389
+ expect(content).toContain("Found");
390
+ expect(content).not.toContain("Index check failed");
391
+ });
392
+
393
+ it("should allow updateMany operations without indexes", async () => {
394
+ const response = await integration.mcpClient().callTool({
395
+ name: "update-many",
396
+ arguments: {
397
+ database: integration.randomDbName(),
398
+ collection: "disabled-test-collection",
399
+ filter: { category: "A" }, // No index, but should be allowed
400
+ update: { $set: { updated: true } },
401
+ },
402
+ });
403
+
404
+ expect(response.isError).toBeFalsy();
405
+ const content = getResponseContent(response.content);
406
+ expect(content).toContain("Matched");
407
+ expect(content).not.toContain("Index check failed");
408
+ });
409
+
410
+ it("should allow deleteMany operations without indexes", async () => {
411
+ const response = await integration.mcpClient().callTool({
412
+ name: "delete-many",
413
+ arguments: {
414
+ database: integration.randomDbName(),
415
+ collection: "disabled-test-collection",
416
+ filter: { value: { $lt: 2 } }, // No index, but should be allowed
417
+ },
418
+ });
419
+
420
+ expect(response.isError).toBeFalsy();
421
+ const content = getResponseContent(response.content);
422
+ expect(content).toContain("Deleted");
423
+ expect(content).not.toContain("Index check failed");
424
+ });
425
+ },
426
+ () => ({
427
+ ...defaultTestConfig,
428
+ indexCheck: false, // Disable indexCheck
429
+ })
430
+ );
431
+ });
432
+
433
+ describe("indexCheck configuration validation", () => {
434
+ describeWithMongoDB(
435
+ "default indexCheck behavior",
436
+ (integration) => {
437
+ it("should allow collection scans by default when indexCheck is not specified", async () => {
438
+ await integration.connectMcpClient();
439
+
440
+ await integration
441
+ .mongoClient()
442
+ .db(integration.randomDbName())
443
+ .collection("default-test-collection")
444
+ .insertOne({ name: "test", value: 1 });
445
+
446
+ const response = await integration.mcpClient().callTool({
447
+ name: "find",
448
+ arguments: {
449
+ database: integration.randomDbName(),
450
+ collection: "default-test-collection",
451
+ filter: { name: "test" }, // No index, should be allowed by default
452
+ },
453
+ });
454
+
455
+ expect(response.isError).toBeFalsy();
456
+ });
457
+ },
458
+ () => ({
459
+ ...defaultTestConfig,
460
+ // indexCheck not specified, should default to false
461
+ })
462
+ );
463
+ });
464
+ });
@@ -1,5 +1,6 @@
1
1
  import { defaultTestConfig, expectDefined, setupIntegrationTest } from "./helpers.js";
2
2
  import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js";
3
+ import { describe, expect, it } from "vitest";
3
4
 
4
5
  describe("Server integration test", () => {
5
6
  describeWithMongoDB(
@@ -47,11 +48,12 @@ describe("Server integration test", () => {
47
48
  it("should return capabilities", () => {
48
49
  const capabilities = integration.mcpClient().getServerCapabilities();
49
50
  expectDefined(capabilities);
50
- expect(capabilities.completions).toBeUndefined();
51
- expect(capabilities.experimental).toBeUndefined();
52
- expectDefined(capabilities?.tools);
53
51
  expectDefined(capabilities?.logging);
54
- expect(capabilities?.prompts).toBeUndefined();
52
+ expectDefined(capabilities?.completions);
53
+ expectDefined(capabilities?.tools);
54
+ expectDefined(capabilities?.resources);
55
+ expect(capabilities.experimental).toBeUndefined();
56
+ expect(capabilities.prompts).toBeUndefined();
55
57
  });
56
58
  });
57
59
  });
@@ -0,0 +1,29 @@
1
+ import { createHmac } from "crypto";
2
+ import { Telemetry } from "../../src/telemetry/telemetry.js";
3
+ import { Session } from "../../src/common/session.js";
4
+ import { config } from "../../src/common/config.js";
5
+ import nodeMachineId from "node-machine-id";
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ describe("Telemetry", () => {
9
+ it("should resolve the actual machine ID", async () => {
10
+ const actualId: string = await nodeMachineId.machineId(true);
11
+
12
+ const actualHashedId = createHmac("sha256", actualId.toUpperCase()).update("atlascli").digest("hex");
13
+
14
+ const telemetry = Telemetry.create(
15
+ new Session({
16
+ apiBaseUrl: "",
17
+ }),
18
+ config
19
+ );
20
+
21
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
22
+ expect(telemetry["isBufferingEvents"]).toBe(true);
23
+
24
+ await telemetry.setupPromise;
25
+
26
+ expect(telemetry.getCommonProperties().device_id).toBe(actualHashedId);
27
+ expect(telemetry["isBufferingEvents"]).toBe(false);
28
+ });
29
+ });
@@ -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