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,67 @@
1
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
2
+ import { setManagedTimeout } from "../../../src/common/managedTimeout.js";
3
+
4
+ describe("setManagedTimeout", () => {
5
+ beforeAll(() => {
6
+ vi.useFakeTimers();
7
+ });
8
+
9
+ afterAll(() => {
10
+ vi.useRealTimers();
11
+ });
12
+
13
+ it("calls the timeout callback", () => {
14
+ const callback = vi.fn();
15
+
16
+ setManagedTimeout(callback, 1000);
17
+
18
+ vi.advanceTimersByTime(1000);
19
+ expect(callback).toHaveBeenCalled();
20
+ });
21
+
22
+ it("does not call the timeout callback if the timeout is cleared", () => {
23
+ const callback = vi.fn();
24
+
25
+ const timeout = setManagedTimeout(callback, 1000);
26
+
27
+ vi.advanceTimersByTime(500);
28
+ timeout.cancel();
29
+ vi.advanceTimersByTime(500);
30
+
31
+ expect(callback).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it("does not call the timeout callback if the timeout is reset", () => {
35
+ const callback = vi.fn();
36
+
37
+ const timeout = setManagedTimeout(callback, 1000);
38
+
39
+ vi.advanceTimersByTime(500);
40
+ timeout.restart();
41
+ vi.advanceTimersByTime(500);
42
+ expect(callback).not.toHaveBeenCalled();
43
+ });
44
+
45
+ describe("if timeout is reset", () => {
46
+ it("does not call the timeout callback within the timeout period", () => {
47
+ const callback = vi.fn();
48
+
49
+ const timeout = setManagedTimeout(callback, 1000);
50
+
51
+ vi.advanceTimersByTime(500);
52
+ timeout.restart();
53
+ vi.advanceTimersByTime(500);
54
+ expect(callback).not.toHaveBeenCalled();
55
+ });
56
+ it("calls the timeout callback after the timeout period", () => {
57
+ const callback = vi.fn();
58
+
59
+ const timeout = setManagedTimeout(callback, 1000);
60
+
61
+ vi.advanceTimersByTime(500);
62
+ timeout.restart();
63
+ vi.advanceTimersByTime(1000);
64
+ expect(callback).toHaveBeenCalled();
65
+ });
66
+ });
67
+ });
@@ -1,10 +1,10 @@
1
- import { jest } from "@jest/globals";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
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
 
6
- jest.mock("@mongosh/service-provider-node-driver");
7
- const MockNodeDriverServiceProvider = NodeDriverServiceProvider as jest.MockedClass<typeof NodeDriverServiceProvider>;
6
+ vi.mock("@mongosh/service-provider-node-driver");
7
+ const MockNodeDriverServiceProvider = vi.mocked(NodeDriverServiceProvider);
8
8
 
9
9
  describe("Session", () => {
10
10
  let session: Session;
@@ -14,9 +14,7 @@ describe("Session", () => {
14
14
  apiBaseUrl: "https://api.test.com",
15
15
  });
16
16
 
17
- MockNodeDriverServiceProvider.connect = jest.fn(() =>
18
- Promise.resolve({} as unknown as NodeDriverServiceProvider)
19
- );
17
+ MockNodeDriverServiceProvider.connect = vi.fn().mockResolvedValue({} as unknown as NodeDriverServiceProvider);
20
18
  });
21
19
 
22
20
  describe("connectToMongoDB", () => {
@@ -48,10 +46,7 @@ describe("Session", () => {
48
46
  await session.connectToMongoDB(testCase.connectionString, config.connectOptions);
49
47
  expect(session.serviceProvider).toBeDefined();
50
48
 
51
- // eslint-disable-next-line @typescript-eslint/unbound-method
52
- const connectMock = MockNodeDriverServiceProvider.connect as jest.Mock<
53
- typeof NodeDriverServiceProvider.connect
54
- >;
49
+ const connectMock = MockNodeDriverServiceProvider.connect;
55
50
  expect(connectMock).toHaveBeenCalledOnce();
56
51
  const connectionString = connectMock.mock.calls[0]?.[0];
57
52
  if (testCase.expectAppName) {
@@ -0,0 +1,150 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { usesIndex, getIndexCheckErrorMessage } from "../../../src/helpers/indexCheck.js";
3
+ import { Document } from "mongodb";
4
+
5
+ describe("indexCheck", () => {
6
+ describe("usesIndex", () => {
7
+ it("should return true for IXSCAN", () => {
8
+ const explainResult: Document = {
9
+ queryPlanner: {
10
+ winningPlan: {
11
+ stage: "IXSCAN",
12
+ },
13
+ },
14
+ };
15
+ expect(usesIndex(explainResult)).toBe(true);
16
+ });
17
+
18
+ it("should return true for COUNT_SCAN", () => {
19
+ const explainResult: Document = {
20
+ queryPlanner: {
21
+ winningPlan: {
22
+ stage: "COUNT_SCAN",
23
+ },
24
+ },
25
+ };
26
+ expect(usesIndex(explainResult)).toBe(true);
27
+ });
28
+
29
+ it("should return true for IDHACK", () => {
30
+ const explainResult: Document = {
31
+ queryPlanner: {
32
+ winningPlan: {
33
+ stage: "IDHACK",
34
+ },
35
+ },
36
+ };
37
+ expect(usesIndex(explainResult)).toBe(true);
38
+ });
39
+
40
+ it("should return true for EXPRESS_IXSCAN (MongoDB 8.0+)", () => {
41
+ const explainResult: Document = {
42
+ queryPlanner: {
43
+ winningPlan: {
44
+ stage: "EXPRESS_IXSCAN",
45
+ },
46
+ },
47
+ };
48
+ expect(usesIndex(explainResult)).toBe(true);
49
+ });
50
+
51
+ it("should return true for EXPRESS_CLUSTERED_IXSCAN (MongoDB 8.0+)", () => {
52
+ const explainResult: Document = {
53
+ queryPlanner: {
54
+ winningPlan: {
55
+ stage: "EXPRESS_CLUSTERED_IXSCAN",
56
+ },
57
+ },
58
+ };
59
+ expect(usesIndex(explainResult)).toBe(true);
60
+ });
61
+
62
+ it("should return true for EXPRESS_UPDATE (MongoDB 8.0+)", () => {
63
+ const explainResult: Document = {
64
+ queryPlanner: {
65
+ winningPlan: {
66
+ stage: "EXPRESS_UPDATE",
67
+ },
68
+ },
69
+ };
70
+ expect(usesIndex(explainResult)).toBe(true);
71
+ });
72
+
73
+ it("should return true for EXPRESS_DELETE (MongoDB 8.0+)", () => {
74
+ const explainResult: Document = {
75
+ queryPlanner: {
76
+ winningPlan: {
77
+ stage: "EXPRESS_DELETE",
78
+ },
79
+ },
80
+ };
81
+ expect(usesIndex(explainResult)).toBe(true);
82
+ });
83
+
84
+ it("should return false for COLLSCAN", () => {
85
+ const explainResult: Document = {
86
+ queryPlanner: {
87
+ winningPlan: {
88
+ stage: "COLLSCAN",
89
+ },
90
+ },
91
+ };
92
+ expect(usesIndex(explainResult)).toBe(false);
93
+ });
94
+
95
+ it("should return true for nested IXSCAN in inputStage", () => {
96
+ const explainResult: Document = {
97
+ queryPlanner: {
98
+ winningPlan: {
99
+ stage: "LIMIT",
100
+ inputStage: {
101
+ stage: "IXSCAN",
102
+ },
103
+ },
104
+ },
105
+ };
106
+ expect(usesIndex(explainResult)).toBe(true);
107
+ });
108
+
109
+ it("should return true for nested EXPRESS_IXSCAN in inputStage", () => {
110
+ const explainResult: Document = {
111
+ queryPlanner: {
112
+ winningPlan: {
113
+ stage: "SORT",
114
+ inputStage: {
115
+ stage: "EXPRESS_IXSCAN",
116
+ },
117
+ },
118
+ },
119
+ };
120
+ expect(usesIndex(explainResult)).toBe(true);
121
+ });
122
+
123
+ it("should return false for unknown stage types", () => {
124
+ const explainResult: Document = {
125
+ queryPlanner: {
126
+ winningPlan: {
127
+ stage: "UNKNOWN_STAGE",
128
+ },
129
+ },
130
+ };
131
+ expect(usesIndex(explainResult)).toBe(false);
132
+ });
133
+
134
+ it("should handle missing queryPlanner", () => {
135
+ const explainResult: Document = {};
136
+ expect(usesIndex(explainResult)).toBe(false);
137
+ });
138
+ });
139
+
140
+ describe("getIndexCheckErrorMessage", () => {
141
+ it("should generate appropriate error message", () => {
142
+ const message = getIndexCheckErrorMessage("testdb", "testcoll", "find");
143
+ expect(message).toContain("Index check failed");
144
+ expect(message).toContain("testdb.testcoll");
145
+ expect(message).toContain("find operation");
146
+ expect(message).toContain("collection scan (COLLSCAN)");
147
+ expect(message).toContain("MDB_MCP_INDEX_CHECK");
148
+ });
149
+ });
150
+ });
@@ -1,36 +1,37 @@
1
1
  import { ApiClient } from "../../src/common/atlas/apiClient.js";
2
- import { Session } from "../../src/session.js";
3
- import { Telemetry } from "../../src/telemetry/telemetry.js";
2
+ import { Session } from "../../src/common/session.js";
3
+ import { DEVICE_ID_TIMEOUT, Telemetry } from "../../src/telemetry/telemetry.js";
4
4
  import { BaseEvent, TelemetryResult } from "../../src/telemetry/types.js";
5
5
  import { EventCache } from "../../src/telemetry/eventCache.js";
6
- import { config } from "../../src/config.js";
7
- import { jest } from "@jest/globals";
8
- import logger, { LogId } from "../../src/logger.js";
6
+ import { config } from "../../src/common/config.js";
7
+ import { afterEach, beforeEach, describe, it, vi, expect } from "vitest";
8
+ import logger, { LogId } from "../../src/common/logger.js";
9
9
  import { createHmac } from "crypto";
10
+ import type { MockedFunction } from "vitest";
10
11
 
11
12
  // Mock the ApiClient to avoid real API calls
12
- jest.mock("../../src/common/atlas/apiClient.js");
13
- const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
13
+ vi.mock("../../src/common/atlas/apiClient.js");
14
+ const MockApiClient = vi.mocked(ApiClient);
14
15
 
15
16
  // Mock EventCache to control and verify caching behavior
16
- jest.mock("../../src/telemetry/eventCache.js");
17
- const MockEventCache = EventCache as jest.MockedClass<typeof EventCache>;
18
-
19
- const nextTick = () => new Promise((resolve) => process.nextTick(resolve));
17
+ vi.mock("../../src/telemetry/eventCache.js");
18
+ const MockEventCache = vi.mocked(EventCache);
20
19
 
21
20
  describe("Telemetry", () => {
22
21
  const machineId = "test-machine-id";
23
22
  const hashedMachineId = createHmac("sha256", machineId.toUpperCase()).update("atlascli").digest("hex");
24
23
 
25
- let mockApiClient: jest.Mocked<ApiClient>;
26
- let mockEventCache: jest.Mocked<EventCache>;
24
+ let mockApiClient: {
25
+ sendEvents: MockedFunction<(events: BaseEvent[]) => Promise<void>>;
26
+ hasCredentials: MockedFunction<() => boolean>;
27
+ };
28
+ let mockEventCache: {
29
+ getEvents: MockedFunction<() => BaseEvent[]>;
30
+ clearEvents: MockedFunction<() => Promise<void>>;
31
+ appendEvents: MockedFunction<(events: BaseEvent[]) => Promise<void>>;
32
+ };
27
33
  let session: Session;
28
34
  let telemetry: Telemetry;
29
- let telemetryConfig: {
30
- eventCache: EventCache;
31
- getRawMachineId: () => Promise<string>;
32
- getContainerEnv: () => Promise<boolean>;
33
- };
34
35
 
35
36
  // Helper function to create properly typed test events
36
37
  function createTestEvent(options?: {
@@ -84,53 +85,52 @@ describe("Telemetry", () => {
84
85
  expect(appendEvents.length).toBe(appendEventsCalls);
85
86
 
86
87
  if (sendEventsCalledWith) {
87
- expect(sendEvents[0]?.[0]).toMatchObject(sendEventsCalledWith);
88
+ expect(sendEvents[0]?.[0]).toEqual(
89
+ sendEventsCalledWith.map((event) => ({
90
+ ...event,
91
+ properties: {
92
+ ...telemetry.getCommonProperties(),
93
+ ...event.properties,
94
+ },
95
+ }))
96
+ );
88
97
  }
89
98
 
90
99
  if (appendEventsCalledWith) {
91
- expect(appendEvents[0]?.[0]).toMatchObject(appendEventsCalledWith);
100
+ expect(appendEvents[0]?.[0]).toEqual(appendEventsCalledWith);
92
101
  }
93
102
  }
94
103
 
95
104
  beforeEach(() => {
96
105
  // Reset mocks before each test
97
- jest.clearAllMocks();
106
+ vi.clearAllMocks();
98
107
 
99
108
  // Setup mocked API client
100
- mockApiClient = new MockApiClient({ baseUrl: "" }) as jest.Mocked<ApiClient>;
101
- //@ts-expect-error This is a workaround
102
- mockApiClient.sendEvents = jest.fn<() => undefined>().mockResolvedValue(undefined);
103
- mockApiClient.hasCredentials = jest.fn<() => boolean>().mockReturnValue(true);
109
+ mockApiClient = vi.mocked(new MockApiClient({ baseUrl: "" }));
110
+
111
+ mockApiClient.sendEvents = vi.fn().mockResolvedValue(undefined);
112
+ mockApiClient.hasCredentials = vi.fn().mockReturnValue(true);
104
113
 
105
114
  // Setup mocked EventCache
106
- mockEventCache = new MockEventCache() as jest.Mocked<EventCache>;
107
- //@ts-expect-error This is a workaround
108
- mockEventCache.getEvents = jest.fn().mockReturnValue([]);
109
- //@ts-expect-error This is a workaround
110
- mockEventCache.clearEvents = jest.fn().mockResolvedValue(undefined);
111
- //@ts-expect-error This is a workaround
112
- mockEventCache.appendEvents = jest.fn().mockResolvedValue(undefined);
113
- //@ts-expect-error This is a workaround
114
- MockEventCache.getInstance = jest.fn().mockReturnValue(mockEventCache);
115
+ mockEventCache = new MockEventCache() as unknown as typeof mockEventCache;
116
+ mockEventCache.getEvents = vi.fn().mockReturnValue([]);
117
+ mockEventCache.clearEvents = vi.fn().mockResolvedValue(undefined);
118
+ mockEventCache.appendEvents = vi.fn().mockResolvedValue(undefined);
119
+ MockEventCache.getInstance = vi.fn().mockReturnValue(mockEventCache as unknown as EventCache);
115
120
 
116
121
  // Create a simplified session with our mocked API client
117
122
  session = {
118
- apiClient: mockApiClient,
123
+ apiClient: mockApiClient as unknown as ApiClient,
119
124
  sessionId: "test-session-id",
120
125
  agentRunner: { name: "test-agent", version: "1.0.0" } as const,
121
- //@ts-expect-error This is a workaround
122
- close: jest.fn().mockResolvedValue(undefined),
123
- //@ts-expect-error This is a workaround
124
- setAgentRunner: jest.fn().mockResolvedValue(undefined),
126
+ close: vi.fn().mockResolvedValue(undefined),
127
+ setAgentRunner: vi.fn().mockResolvedValue(undefined),
125
128
  } as unknown as Session;
126
129
 
127
- telemetryConfig = {
128
- eventCache: mockEventCache,
130
+ telemetry = Telemetry.create(session, config, {
131
+ eventCache: mockEventCache as unknown as EventCache,
129
132
  getRawMachineId: () => Promise.resolve(machineId),
130
- getContainerEnv: () => Promise.resolve(false),
131
- };
132
-
133
- telemetry = Telemetry.create(session, config, telemetryConfig);
133
+ });
134
134
 
135
135
  config.telemetry = "enabled";
136
136
  });
@@ -140,8 +140,9 @@ describe("Telemetry", () => {
140
140
  it("should send events successfully", async () => {
141
141
  const testEvent = createTestEvent();
142
142
 
143
- telemetry.emitEvents([testEvent]);
144
- await nextTick(); // wait for the event to be sent
143
+ await telemetry.setupPromise;
144
+
145
+ await telemetry.emitEvents([testEvent]);
145
146
 
146
147
  verifyMockCalls({
147
148
  sendEventsCalls: 1,
@@ -155,8 +156,9 @@ describe("Telemetry", () => {
155
156
 
156
157
  const testEvent = createTestEvent();
157
158
 
158
- telemetry.emitEvents([testEvent]);
159
- await nextTick(); // wait for the event to be sent
159
+ await telemetry.setupPromise;
160
+
161
+ await telemetry.emitEvents([testEvent]);
160
162
 
161
163
  verifyMockCalls({
162
164
  sendEventsCalls: 1,
@@ -179,8 +181,9 @@ describe("Telemetry", () => {
179
181
  // Set up mock to return cached events
180
182
  mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]);
181
183
 
182
- telemetry.emitEvents([newEvent]);
183
- await nextTick(); // wait for the event to be sent
184
+ await telemetry.setupPromise;
185
+
186
+ await telemetry.emitEvents([newEvent]);
184
187
 
185
188
  verifyMockCalls({
186
189
  sendEventsCalls: 1,
@@ -190,6 +193,10 @@ describe("Telemetry", () => {
190
193
  });
191
194
 
192
195
  it("should correctly add common properties to events", async () => {
196
+ await telemetry.setupPromise;
197
+
198
+ const commonProps = telemetry.getCommonProperties();
199
+
193
200
  // Use explicit type assertion
194
201
  const expectedProps: Record<string, string> = {
195
202
  mcp_client_version: "1.0.0",
@@ -200,86 +207,48 @@ describe("Telemetry", () => {
200
207
  device_id: hashedMachineId,
201
208
  };
202
209
 
203
- const testEvent = createTestEvent();
204
-
205
- telemetry.emitEvents([testEvent]);
206
- await nextTick(); // wait for the event to be sent
207
-
208
- const checkEvent = {
209
- ...testEvent,
210
- properties: {
211
- ...testEvent.properties,
212
- ...expectedProps,
213
- },
214
- };
215
-
216
- verifyMockCalls({
217
- sendEventsCalls: 1,
218
- clearEventsCalls: 1,
219
- sendEventsCalledWith: [checkEvent],
220
- });
210
+ expect(commonProps).toMatchObject(expectedProps);
221
211
  });
222
212
 
223
- it("should send cache new event while sending another event", async () => {
224
- const newEvent = createTestEvent({
225
- command: "new-command",
226
- component: "new-component",
213
+ describe("machine ID resolution", () => {
214
+ beforeEach(() => {
215
+ vi.clearAllMocks();
216
+ vi.useFakeTimers();
227
217
  });
228
218
 
229
- const newEvent2 = createTestEvent({
230
- command: "new-command-2",
231
- component: "new-component-2",
219
+ afterEach(() => {
220
+ vi.clearAllMocks();
221
+ vi.useRealTimers();
232
222
  });
233
223
 
234
- telemetry.emitEvents([newEvent]);
235
- telemetry.emitEvents([newEvent2]);
224
+ it("should successfully resolve the machine ID", async () => {
225
+ telemetry = Telemetry.create(session, config, {
226
+ getRawMachineId: () => Promise.resolve(machineId),
227
+ });
236
228
 
237
- await nextTick(); // wait for the event to be sent
229
+ expect(telemetry["isBufferingEvents"]).toBe(true);
230
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
238
231
 
239
- verifyMockCalls({
240
- sendEventsCalls: 1,
241
- clearEventsCalls: 1,
242
- appendEventsCalls: 1,
243
- sendEventsCalledWith: [newEvent],
244
- appendEventsCalledWith: [newEvent2],
245
- });
246
- });
232
+ await telemetry.setupPromise;
247
233
 
248
- describe("machine ID resolution", () => {
249
- it("should successfully resolve the machine ID", async () => {
250
- const testEvent = createTestEvent();
251
-
252
- telemetry.emitEvents([testEvent]);
253
- await nextTick(); // wait for the event to be sent
254
-
255
- const checkEvent = {
256
- ...testEvent,
257
- properties: {
258
- ...testEvent.properties,
259
- device_id: hashedMachineId,
260
- },
261
- };
262
-
263
- verifyMockCalls({
264
- sendEventsCalls: 1,
265
- clearEventsCalls: 1,
266
- sendEventsCalledWith: [checkEvent],
267
- });
234
+ expect(telemetry["isBufferingEvents"]).toBe(false);
235
+ expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId);
268
236
  });
269
237
 
270
238
  it("should handle machine ID resolution failure", async () => {
271
- const loggerSpy = jest.spyOn(logger, "debug");
239
+ const loggerSpy = vi.spyOn(logger, "debug");
272
240
 
273
241
  telemetry = Telemetry.create(session, config, {
274
- ...telemetryConfig,
275
242
  getRawMachineId: () => Promise.reject(new Error("Failed to get device ID")),
276
243
  });
277
244
 
278
- const testEvent = createTestEvent();
245
+ expect(telemetry["isBufferingEvents"]).toBe(true);
246
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
279
247
 
280
- telemetry.emitEvents([testEvent]);
248
+ await telemetry.setupPromise;
281
249
 
282
- await nextTick(); // wait for the event to be sent
250
+ expect(telemetry["isBufferingEvents"]).toBe(false);
251
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
283
252
 
284
253
  expect(loggerSpy).toHaveBeenCalledWith(
285
254
  LogId.telemetryDeviceIdFailure,
@@ -288,28 +257,27 @@ describe("Telemetry", () => {
288
257
  );
289
258
  });
290
259
 
291
- it("should timeout if machine ID resolution takes too long", () => {
292
- const loggerSpy = jest.spyOn(logger, "debug");
293
-
294
- jest.useFakeTimers();
260
+ it("should timeout if machine ID resolution takes too long", async () => {
261
+ const loggerSpy = vi.spyOn(logger, "debug");
295
262
 
296
- telemetry = Telemetry.create(session, config, {
297
- ...telemetryConfig,
298
- getRawMachineId: () => new Promise(() => {}), // Never resolves
299
- });
263
+ telemetry = Telemetry.create(session, config, { getRawMachineId: () => new Promise(() => {}) });
300
264
 
301
- const testEvent = createTestEvent();
265
+ expect(telemetry["isBufferingEvents"]).toBe(true);
266
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
302
267
 
303
- telemetry.emitEvents([testEvent]);
268
+ vi.advanceTimersByTime(DEVICE_ID_TIMEOUT / 2);
304
269
 
305
- jest.advanceTimersByTime(5000);
270
+ // Make sure the timeout doesn't happen prematurely.
271
+ expect(telemetry["isBufferingEvents"]).toBe(true);
272
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
306
273
 
307
- jest.useRealTimers();
274
+ vi.advanceTimersByTime(DEVICE_ID_TIMEOUT);
308
275
 
309
- expect(loggerSpy).toHaveBeenCalledTimes(2);
276
+ await telemetry.setupPromise;
310
277
 
311
- expect(loggerSpy).toHaveBeenNthCalledWith(
312
- 2,
278
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
279
+ expect(telemetry["isBufferingEvents"]).toBe(false);
280
+ expect(loggerSpy).toHaveBeenCalledWith(
313
281
  LogId.telemetryDeviceIdTimeout,
314
282
  "telemetry",
315
283
  "Device ID retrieval timed out"
@@ -330,12 +298,9 @@ describe("Telemetry", () => {
330
298
  it("should not send events", async () => {
331
299
  const testEvent = createTestEvent();
332
300
 
333
- telemetry.emitEvents([testEvent]);
334
- await nextTick(); // wait for the event to be sent
301
+ await telemetry.emitEvents([testEvent]);
335
302
 
336
- verifyMockCalls({
337
- sendEventsCalls: 0,
338
- });
303
+ verifyMockCalls();
339
304
  });
340
305
  });
341
306
 
@@ -358,12 +323,9 @@ describe("Telemetry", () => {
358
323
  it("should not send events", async () => {
359
324
  const testEvent = createTestEvent();
360
325
 
361
- telemetry.emitEvents([testEvent]);
362
- await nextTick(); // wait for the event to be sent
326
+ await telemetry.emitEvents([testEvent]);
363
327
 
364
- verifyMockCalls({
365
- sendEventsCalls: 0,
366
- });
328
+ verifyMockCalls();
367
329
  });
368
330
  });
369
331
  });
@@ -1,15 +1,15 @@
1
1
  import { Decimal128, MaxKey, MinKey, ObjectId, Timestamp, UUID } from "bson";
2
- import { createEJsonTransport, EJsonReadBuffer } from "../../src/helpers/EJsonTransport.js";
2
+ import { createStdioTransport, EJsonReadBuffer } from "../../../src/transports/stdio.js";
3
3
  import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import { Readable } from "stream";
7
7
  import { ReadBuffer } from "@modelcontextprotocol/sdk/shared/stdio.js";
8
-
9
- describe("EJsonTransport", () => {
8
+ import { describe, expect, it, beforeEach, afterEach } from "vitest";
9
+ describe("stdioTransport", () => {
10
10
  let transport: StdioServerTransport;
11
11
  beforeEach(async () => {
12
- transport = createEJsonTransport();
12
+ transport = createStdioTransport();
13
13
  await transport.start();
14
14
  });
15
15
 
@@ -0,0 +1,11 @@
1
+ import "vitest";
2
+
3
+ declare module "vitest" {
4
+ interface Assertion<T = unknown> {
5
+ toIncludeSameMembers<U>(expected: U[]): T;
6
+ }
7
+
8
+ interface AsymmetricMatchersContaining {
9
+ toIncludeSameMembers<T>(expected: T[]): unknown;
10
+ }
11
+ }
package/tsconfig.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "extends": "./tsconfig.build.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": ".",
5
- "types": ["jest"],
6
5
  "skipLibCheck": true
7
6
  },
8
7
  "include": ["**/*"]
@@ -2,8 +2,7 @@
2
2
  "extends": "./tsconfig.build.json",
3
3
  "compilerOptions": {
4
4
  "isolatedModules": true,
5
- "allowSyntheticDefaultImports": true,
6
- "types": ["jest", "jest-extended"]
5
+ "allowSyntheticDefaultImports": true
7
6
  },
8
7
  "include": ["src/**/*.ts", "tests/**/*.ts"]
9
8
  }