mongodb-mcp-server 0.1.1 → 0.1.2

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