mongodb-mcp-server 0.1.2 → 0.1.3

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 (55) hide show
  1. package/.github/pull_request_template.md +5 -0
  2. package/.github/workflows/code_health.yaml +3 -3
  3. package/.github/workflows/docker.yaml +1 -1
  4. package/.smithery/smithery.yaml +10 -0
  5. package/README.md +15 -1
  6. package/dist/config.js +1 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/errors.js +1 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/helpers/indexCheck.js +63 -0
  11. package/dist/helpers/indexCheck.js.map +1 -0
  12. package/dist/logger.js +0 -1
  13. package/dist/logger.js.map +1 -1
  14. package/dist/server.js +1 -1
  15. package/dist/server.js.map +1 -1
  16. package/dist/telemetry/telemetry.js +78 -115
  17. package/dist/telemetry/telemetry.js.map +1 -1
  18. package/dist/tools/mongodb/delete/deleteMany.js +18 -0
  19. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
  20. package/dist/tools/mongodb/metadata/explain.js +1 -1
  21. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  22. package/dist/tools/mongodb/mongodbTool.js +10 -0
  23. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  24. package/dist/tools/mongodb/read/aggregate.js +9 -0
  25. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  26. package/dist/tools/mongodb/read/count.js +13 -0
  27. package/dist/tools/mongodb/read/count.js.map +1 -1
  28. package/dist/tools/mongodb/read/find.js +7 -0
  29. package/dist/tools/mongodb/read/find.js.map +1 -1
  30. package/dist/tools/mongodb/update/updateMany.js +20 -0
  31. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  32. package/dist/tools/tool.js +4 -4
  33. package/dist/tools/tool.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/config.ts +2 -0
  36. package/src/errors.ts +1 -0
  37. package/src/helpers/indexCheck.ts +83 -0
  38. package/src/logger.ts +0 -1
  39. package/src/server.ts +1 -1
  40. package/src/telemetry/telemetry.ts +98 -150
  41. package/src/telemetry/types.ts +0 -1
  42. package/src/tools/mongodb/delete/deleteMany.ts +20 -0
  43. package/src/tools/mongodb/metadata/explain.ts +1 -1
  44. package/src/tools/mongodb/mongodbTool.ts +10 -0
  45. package/src/tools/mongodb/read/aggregate.ts +11 -0
  46. package/src/tools/mongodb/read/count.ts +15 -0
  47. package/src/tools/mongodb/read/find.ts +9 -0
  48. package/src/tools/mongodb/update/updateMany.ts +22 -0
  49. package/src/tools/tool.ts +5 -5
  50. package/tests/integration/indexCheck.test.ts +463 -0
  51. package/tests/integration/server.test.ts +5 -4
  52. package/tests/integration/telemetry.test.ts +28 -0
  53. package/tests/integration/tools/mongodb/mongodbHelpers.ts +1 -0
  54. package/tests/unit/indexCheck.test.ts +149 -0
  55. package/tests/unit/telemetry.test.ts +58 -106
@@ -1,6 +1,6 @@
1
1
  import { ApiClient } from "../../src/common/atlas/apiClient.js";
2
2
  import { Session } from "../../src/session.js";
3
- import { Telemetry } from "../../src/telemetry/telemetry.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
6
  import { config } from "../../src/config.js";
@@ -16,8 +16,6 @@ const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
16
16
  jest.mock("../../src/telemetry/eventCache.js");
17
17
  const MockEventCache = EventCache as jest.MockedClass<typeof EventCache>;
18
18
 
19
- const nextTick = () => new Promise((resolve) => process.nextTick(resolve));
20
-
21
19
  describe("Telemetry", () => {
22
20
  const machineId = "test-machine-id";
23
21
  const hashedMachineId = createHmac("sha256", machineId.toUpperCase()).update("atlascli").digest("hex");
@@ -26,11 +24,6 @@ describe("Telemetry", () => {
26
24
  let mockEventCache: jest.Mocked<EventCache>;
27
25
  let session: Session;
28
26
  let telemetry: Telemetry;
29
- let telemetryConfig: {
30
- eventCache: EventCache;
31
- getRawMachineId: () => Promise<string>;
32
- getContainerEnv: () => Promise<boolean>;
33
- };
34
27
 
35
28
  // Helper function to create properly typed test events
36
29
  function createTestEvent(options?: {
@@ -84,11 +77,19 @@ describe("Telemetry", () => {
84
77
  expect(appendEvents.length).toBe(appendEventsCalls);
85
78
 
86
79
  if (sendEventsCalledWith) {
87
- expect(sendEvents[0]?.[0]).toMatchObject(sendEventsCalledWith);
80
+ expect(sendEvents[0]?.[0]).toEqual(
81
+ sendEventsCalledWith.map((event) => ({
82
+ ...event,
83
+ properties: {
84
+ ...telemetry.getCommonProperties(),
85
+ ...event.properties,
86
+ },
87
+ }))
88
+ );
88
89
  }
89
90
 
90
91
  if (appendEventsCalledWith) {
91
- expect(appendEvents[0]?.[0]).toMatchObject(appendEventsCalledWith);
92
+ expect(appendEvents[0]?.[0]).toEqual(appendEventsCalledWith);
92
93
  }
93
94
  }
94
95
 
@@ -124,13 +125,10 @@ describe("Telemetry", () => {
124
125
  setAgentRunner: jest.fn().mockResolvedValue(undefined),
125
126
  } as unknown as Session;
126
127
 
127
- telemetryConfig = {
128
+ telemetry = Telemetry.create(session, config, {
128
129
  eventCache: mockEventCache,
129
130
  getRawMachineId: () => Promise.resolve(machineId),
130
- getContainerEnv: () => Promise.resolve(false),
131
- };
132
-
133
- telemetry = Telemetry.create(session, config, telemetryConfig);
131
+ });
134
132
 
135
133
  config.telemetry = "enabled";
136
134
  });
@@ -140,8 +138,7 @@ describe("Telemetry", () => {
140
138
  it("should send events successfully", async () => {
141
139
  const testEvent = createTestEvent();
142
140
 
143
- telemetry.emitEvents([testEvent]);
144
- await nextTick(); // wait for the event to be sent
141
+ await telemetry.emitEvents([testEvent]);
145
142
 
146
143
  verifyMockCalls({
147
144
  sendEventsCalls: 1,
@@ -155,8 +152,7 @@ describe("Telemetry", () => {
155
152
 
156
153
  const testEvent = createTestEvent();
157
154
 
158
- telemetry.emitEvents([testEvent]);
159
- await nextTick(); // wait for the event to be sent
155
+ await telemetry.emitEvents([testEvent]);
160
156
 
161
157
  verifyMockCalls({
162
158
  sendEventsCalls: 1,
@@ -179,8 +175,7 @@ describe("Telemetry", () => {
179
175
  // Set up mock to return cached events
180
176
  mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]);
181
177
 
182
- telemetry.emitEvents([newEvent]);
183
- await nextTick(); // wait for the event to be sent
178
+ await telemetry.emitEvents([newEvent]);
184
179
 
185
180
  verifyMockCalls({
186
181
  sendEventsCalls: 1,
@@ -189,7 +184,9 @@ describe("Telemetry", () => {
189
184
  });
190
185
  });
191
186
 
192
- it("should correctly add common properties to events", async () => {
187
+ it("should correctly add common properties to events", () => {
188
+ const commonProps = telemetry.getCommonProperties();
189
+
193
190
  // Use explicit type assertion
194
191
  const expectedProps: Record<string, string> = {
195
192
  mcp_client_version: "1.0.0",
@@ -200,86 +197,48 @@ describe("Telemetry", () => {
200
197
  device_id: hashedMachineId,
201
198
  };
202
199
 
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
- });
200
+ expect(commonProps).toMatchObject(expectedProps);
221
201
  });
222
202
 
223
- it("should send cache new event while sending another event", async () => {
224
- const newEvent = createTestEvent({
225
- command: "new-command",
226
- component: "new-component",
203
+ describe("machine ID resolution", () => {
204
+ beforeEach(() => {
205
+ jest.clearAllMocks();
206
+ jest.useFakeTimers();
227
207
  });
228
208
 
229
- const newEvent2 = createTestEvent({
230
- command: "new-command-2",
231
- component: "new-component-2",
209
+ afterEach(() => {
210
+ jest.clearAllMocks();
211
+ jest.useRealTimers();
232
212
  });
233
213
 
234
- telemetry.emitEvents([newEvent]);
235
- telemetry.emitEvents([newEvent2]);
214
+ it("should successfully resolve the machine ID", async () => {
215
+ telemetry = Telemetry.create(session, config, {
216
+ getRawMachineId: () => Promise.resolve(machineId),
217
+ });
236
218
 
237
- await nextTick(); // wait for the event to be sent
219
+ expect(telemetry["isBufferingEvents"]).toBe(true);
220
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
238
221
 
239
- verifyMockCalls({
240
- sendEventsCalls: 1,
241
- clearEventsCalls: 1,
242
- appendEventsCalls: 1,
243
- sendEventsCalledWith: [newEvent],
244
- appendEventsCalledWith: [newEvent2],
245
- });
246
- });
222
+ await telemetry.deviceIdPromise;
247
223
 
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
- });
224
+ expect(telemetry["isBufferingEvents"]).toBe(false);
225
+ expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId);
268
226
  });
269
227
 
270
228
  it("should handle machine ID resolution failure", async () => {
271
229
  const loggerSpy = jest.spyOn(logger, "debug");
272
230
 
273
231
  telemetry = Telemetry.create(session, config, {
274
- ...telemetryConfig,
275
232
  getRawMachineId: () => Promise.reject(new Error("Failed to get device ID")),
276
233
  });
277
234
 
278
- const testEvent = createTestEvent();
235
+ expect(telemetry["isBufferingEvents"]).toBe(true);
236
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
279
237
 
280
- telemetry.emitEvents([testEvent]);
238
+ await telemetry.deviceIdPromise;
281
239
 
282
- await nextTick(); // wait for the event to be sent
240
+ expect(telemetry["isBufferingEvents"]).toBe(false);
241
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
283
242
 
284
243
  expect(loggerSpy).toHaveBeenCalledWith(
285
244
  LogId.telemetryDeviceIdFailure,
@@ -288,28 +247,27 @@ describe("Telemetry", () => {
288
247
  );
289
248
  });
290
249
 
291
- it("should timeout if machine ID resolution takes too long", () => {
250
+ it("should timeout if machine ID resolution takes too long", async () => {
292
251
  const loggerSpy = jest.spyOn(logger, "debug");
293
252
 
294
- jest.useFakeTimers();
295
-
296
- telemetry = Telemetry.create(session, config, {
297
- ...telemetryConfig,
298
- getRawMachineId: () => new Promise(() => {}), // Never resolves
299
- });
253
+ telemetry = Telemetry.create(session, config, { getRawMachineId: () => new Promise(() => {}) });
300
254
 
301
- const testEvent = createTestEvent();
255
+ expect(telemetry["isBufferingEvents"]).toBe(true);
256
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
302
257
 
303
- telemetry.emitEvents([testEvent]);
258
+ jest.advanceTimersByTime(DEVICE_ID_TIMEOUT / 2);
304
259
 
305
- jest.advanceTimersByTime(5000);
260
+ // Make sure the timeout doesn't happen prematurely.
261
+ expect(telemetry["isBufferingEvents"]).toBe(true);
262
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
306
263
 
307
- jest.useRealTimers();
264
+ jest.advanceTimersByTime(DEVICE_ID_TIMEOUT);
308
265
 
309
- expect(loggerSpy).toHaveBeenCalledTimes(2);
266
+ await telemetry.deviceIdPromise;
310
267
 
311
- expect(loggerSpy).toHaveBeenNthCalledWith(
312
- 2,
268
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
269
+ expect(telemetry["isBufferingEvents"]).toBe(false);
270
+ expect(loggerSpy).toHaveBeenCalledWith(
313
271
  LogId.telemetryDeviceIdTimeout,
314
272
  "telemetry",
315
273
  "Device ID retrieval timed out"
@@ -330,12 +288,9 @@ describe("Telemetry", () => {
330
288
  it("should not send events", async () => {
331
289
  const testEvent = createTestEvent();
332
290
 
333
- telemetry.emitEvents([testEvent]);
334
- await nextTick(); // wait for the event to be sent
291
+ await telemetry.emitEvents([testEvent]);
335
292
 
336
- verifyMockCalls({
337
- sendEventsCalls: 0,
338
- });
293
+ verifyMockCalls();
339
294
  });
340
295
  });
341
296
 
@@ -358,12 +313,9 @@ describe("Telemetry", () => {
358
313
  it("should not send events", async () => {
359
314
  const testEvent = createTestEvent();
360
315
 
361
- telemetry.emitEvents([testEvent]);
362
- await nextTick(); // wait for the event to be sent
316
+ await telemetry.emitEvents([testEvent]);
363
317
 
364
- verifyMockCalls({
365
- sendEventsCalls: 0,
366
- });
318
+ verifyMockCalls();
367
319
  });
368
320
  });
369
321
  });