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
@@ -7,152 +7,114 @@ import { MACHINE_METADATA } from "./constants.js";
7
7
  import { EventCache } from "./eventCache.js";
8
8
  import nodeMachineId from "node-machine-id";
9
9
  import { getDeviceId } from "@mongodb-js/device-id";
10
- import fs from "fs/promises";
11
-
12
- async function fileExists(filePath: string): Promise<boolean> {
13
- try {
14
- await fs.access(filePath, fs.constants.F_OK);
15
- return true; // File exists
16
- } catch (e: unknown) {
17
- if (
18
- e instanceof Error &&
19
- (
20
- e as Error & {
21
- code: string;
22
- }
23
- ).code === "ENOENT"
24
- ) {
25
- return false; // File does not exist
26
- }
27
- throw e; // Re-throw unexpected errors
28
- }
29
- }
30
10
 
31
- async function isContainerized(): Promise<boolean> {
32
- if (process.env.container) {
33
- return true;
34
- }
35
-
36
- const exists = await Promise.all(["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(fileExists));
11
+ type EventResult = {
12
+ success: boolean;
13
+ error?: Error;
14
+ };
37
15
 
38
- return exists.includes(true);
39
- }
16
+ export const DEVICE_ID_TIMEOUT = 3000;
40
17
 
41
18
  export class Telemetry {
19
+ private isBufferingEvents: boolean = true;
20
+ /** Resolves when the device ID is retrieved or timeout occurs */
21
+ public deviceIdPromise: Promise<string> | undefined;
42
22
  private deviceIdAbortController = new AbortController();
43
23
  private eventCache: EventCache;
44
24
  private getRawMachineId: () => Promise<string>;
45
- private getContainerEnv: () => Promise<boolean>;
46
- private cachedCommonProperties?: CommonProperties;
47
- private flushing: boolean = false;
48
25
 
49
26
  private constructor(
50
27
  private readonly session: Session,
51
28
  private readonly userConfig: UserConfig,
52
- {
53
- eventCache,
54
- getRawMachineId,
55
- getContainerEnv,
56
- }: {
57
- eventCache: EventCache;
58
- getRawMachineId: () => Promise<string>;
59
- getContainerEnv: () => Promise<boolean>;
60
- }
29
+ private readonly commonProperties: CommonProperties,
30
+ { eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> }
61
31
  ) {
62
32
  this.eventCache = eventCache;
63
33
  this.getRawMachineId = getRawMachineId;
64
- this.getContainerEnv = getContainerEnv;
65
34
  }
66
35
 
67
36
  static create(
68
37
  session: Session,
69
38
  userConfig: UserConfig,
70
39
  {
40
+ commonProperties = { ...MACHINE_METADATA },
71
41
  eventCache = EventCache.getInstance(),
72
42
  getRawMachineId = () => nodeMachineId.machineId(true),
73
- getContainerEnv = isContainerized,
74
43
  }: {
75
44
  eventCache?: EventCache;
76
45
  getRawMachineId?: () => Promise<string>;
77
- getContainerEnv?: () => Promise<boolean>;
46
+ commonProperties?: CommonProperties;
78
47
  } = {}
79
48
  ): Telemetry {
80
- const instance = new Telemetry(session, userConfig, {
81
- eventCache,
82
- getRawMachineId,
83
- getContainerEnv,
84
- });
49
+ const instance = new Telemetry(session, userConfig, commonProperties, { eventCache, getRawMachineId });
85
50
 
51
+ void instance.start();
86
52
  return instance;
87
53
  }
88
54
 
55
+ private async start(): Promise<void> {
56
+ if (!this.isTelemetryEnabled()) {
57
+ return;
58
+ }
59
+ this.deviceIdPromise = getDeviceId({
60
+ getMachineId: () => this.getRawMachineId(),
61
+ onError: (reason, error) => {
62
+ switch (reason) {
63
+ case "resolutionError":
64
+ logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
65
+ break;
66
+ case "timeout":
67
+ logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out");
68
+ break;
69
+ case "abort":
70
+ // No need to log in the case of aborts
71
+ break;
72
+ }
73
+ },
74
+ abortSignal: this.deviceIdAbortController.signal,
75
+ });
76
+
77
+ this.commonProperties.device_id = await this.deviceIdPromise;
78
+
79
+ this.isBufferingEvents = false;
80
+ }
81
+
89
82
  public async close(): Promise<void> {
90
83
  this.deviceIdAbortController.abort();
91
- await this.flush();
84
+ this.isBufferingEvents = false;
85
+ await this.emitEvents(this.eventCache.getEvents());
92
86
  }
93
87
 
94
88
  /**
95
89
  * Emits events through the telemetry pipeline
96
90
  * @param events - The events to emit
97
91
  */
98
- public emitEvents(events: BaseEvent[]): void {
99
- void this.flush(events);
92
+ public async emitEvents(events: BaseEvent[]): Promise<void> {
93
+ try {
94
+ if (!this.isTelemetryEnabled()) {
95
+ logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
96
+ return;
97
+ }
98
+
99
+ await this.emit(events);
100
+ } catch {
101
+ logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
102
+ }
100
103
  }
101
104
 
102
105
  /**
103
106
  * Gets the common properties for events
104
107
  * @returns Object containing common properties for all events
105
108
  */
106
- private async getCommonProperties(): Promise<CommonProperties> {
107
- if (!this.cachedCommonProperties) {
108
- let deviceId: string | undefined;
109
- let containerEnv: boolean | undefined;
110
- try {
111
- await Promise.all([
112
- getDeviceId({
113
- getMachineId: () => this.getRawMachineId(),
114
- onError: (reason, error) => {
115
- switch (reason) {
116
- case "resolutionError":
117
- logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
118
- break;
119
- case "timeout":
120
- logger.debug(
121
- LogId.telemetryDeviceIdTimeout,
122
- "telemetry",
123
- "Device ID retrieval timed out"
124
- );
125
- break;
126
- case "abort":
127
- // No need to log in the case of aborts
128
- break;
129
- }
130
- },
131
- abortSignal: this.deviceIdAbortController.signal,
132
- }).then((id) => {
133
- deviceId = id;
134
- }),
135
- this.getContainerEnv().then((env) => {
136
- containerEnv = env;
137
- }),
138
- ]);
139
- } catch (error: unknown) {
140
- const err = error instanceof Error ? error : new Error(String(error));
141
- logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", err.message);
142
- }
143
- this.cachedCommonProperties = {
144
- ...MACHINE_METADATA,
145
- mcp_client_version: this.session.agentRunner?.version,
146
- mcp_client_name: this.session.agentRunner?.name,
147
- session_id: this.session.sessionId,
148
- config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
149
- config_connection_string: this.userConfig.connectionString ? "true" : "false",
150
- is_container_env: containerEnv ? "true" : "false",
151
- device_id: deviceId,
152
- };
153
- }
154
-
155
- return this.cachedCommonProperties;
109
+ public getCommonProperties(): CommonProperties {
110
+ return {
111
+ ...this.commonProperties,
112
+ mcp_client_version: this.session.agentRunner?.version,
113
+ mcp_client_name: this.session.agentRunner?.name,
114
+ session_id: this.session.sessionId,
115
+ config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
116
+ config_connection_string: this.userConfig.connectionString ? "true" : "false",
117
+ };
156
118
  }
157
119
 
158
120
  /**
@@ -173,74 +135,60 @@ export class Telemetry {
173
135
  }
174
136
 
175
137
  /**
176
- * Attempts to flush events through authenticated and unauthenticated clients
138
+ * Attempts to emit events through authenticated and unauthenticated clients
177
139
  * Falls back to caching if both attempts fail
178
140
  */
179
- public async flush(events?: BaseEvent[]): Promise<void> {
180
- if (!this.isTelemetryEnabled()) {
181
- logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
182
- return;
183
- }
184
-
185
- if (this.flushing) {
186
- this.eventCache.appendEvents(events ?? []);
187
- process.nextTick(async () => {
188
- // try again if in the middle of a flush
189
- await this.flush();
190
- });
141
+ private async emit(events: BaseEvent[]): Promise<void> {
142
+ if (this.isBufferingEvents) {
143
+ this.eventCache.appendEvents(events);
191
144
  return;
192
145
  }
193
146
 
194
- this.flushing = true;
147
+ const cachedEvents = this.eventCache.getEvents();
148
+ const allEvents = [...cachedEvents, ...events];
195
149
 
196
- try {
197
- const cachedEvents = this.eventCache.getEvents();
198
- const allEvents = [...cachedEvents, ...(events ?? [])];
199
- if (allEvents.length <= 0) {
200
- this.flushing = false;
201
- return;
202
- }
203
-
204
- logger.debug(
205
- LogId.telemetryEmitStart,
206
- "telemetry",
207
- `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
208
- );
150
+ logger.debug(
151
+ LogId.telemetryEmitStart,
152
+ "telemetry",
153
+ `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
154
+ );
209
155
 
210
- await this.sendEvents(this.session.apiClient, allEvents);
156
+ const result = await this.sendEvents(this.session.apiClient, allEvents);
157
+ if (result.success) {
211
158
  this.eventCache.clearEvents();
212
159
  logger.debug(
213
160
  LogId.telemetryEmitSuccess,
214
161
  "telemetry",
215
162
  `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
216
163
  );
217
- } catch (error: unknown) {
218
- logger.debug(
219
- LogId.telemetryEmitFailure,
220
- "telemetry",
221
- `Error sending event to client: ${error instanceof Error ? error.message : String(error)}`
222
- );
223
- this.eventCache.appendEvents(events ?? []);
224
- process.nextTick(async () => {
225
- // try again
226
- await this.flush();
227
- });
164
+ return;
228
165
  }
229
166
 
230
- this.flushing = false;
167
+ logger.debug(
168
+ LogId.telemetryEmitFailure,
169
+ "telemetry",
170
+ `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`
171
+ );
172
+ this.eventCache.appendEvents(events);
231
173
  }
232
174
 
233
175
  /**
234
176
  * Attempts to send events through the provided API client
235
177
  */
236
- private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<void> {
237
- const commonProperties = await this.getCommonProperties();
238
-
239
- await client.sendEvents(
240
- events.map((event) => ({
241
- ...event,
242
- properties: { ...commonProperties, ...event.properties },
243
- }))
244
- );
178
+ private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
179
+ try {
180
+ await client.sendEvents(
181
+ events.map((event) => ({
182
+ ...event,
183
+ properties: { ...this.getCommonProperties(), ...event.properties },
184
+ }))
185
+ );
186
+ return { success: true };
187
+ } catch (error) {
188
+ return {
189
+ success: false,
190
+ error: error instanceof Error ? error : new Error(String(error)),
191
+ };
192
+ }
245
193
  }
246
194
  }
@@ -71,5 +71,4 @@ export type CommonProperties = {
71
71
  config_atlas_auth?: TelemetryBoolSet;
72
72
  config_connection_string?: TelemetryBoolSet;
73
73
  session_id?: string;
74
- is_container_env?: TelemetryBoolSet;
75
74
  } & CommonStaticProperties;
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
+ import { checkIndexUsage } from "../../../helpers/indexCheck.js";
5
6
 
6
7
  export class DeleteManyTool extends MongoDBToolBase {
7
8
  protected name = "delete-many";
@@ -23,6 +24,25 @@ export class DeleteManyTool extends MongoDBToolBase {
23
24
  filter,
24
25
  }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
25
26
  const provider = await this.ensureConnected();
27
+
28
+ // Check if delete operation uses an index if enabled
29
+ if (this.config.indexCheck) {
30
+ await checkIndexUsage(provider, database, collection, "deleteMany", async () => {
31
+ return provider.runCommandWithCheck(database, {
32
+ explain: {
33
+ delete: collection,
34
+ deletes: [
35
+ {
36
+ q: filter || {},
37
+ limit: 0, // 0 means delete all matching documents
38
+ },
39
+ ],
40
+ },
41
+ verbosity: "queryPlanner",
42
+ });
43
+ });
44
+ }
45
+
26
46
  const result = await provider.deleteMany(database, collection, filter);
27
47
 
28
48
  return {
@@ -76,7 +76,7 @@ export class ExplainTool extends MongoDBToolBase {
76
76
  }
77
77
  case "count": {
78
78
  const { query } = method.arguments;
79
- result = await provider.mongoClient.db(database).command({
79
+ result = await provider.runCommandWithCheck(database, {
80
80
  explain: {
81
81
  count: collection,
82
82
  query,
@@ -64,6 +64,16 @@ export abstract class MongoDBToolBase extends ToolBase {
64
64
  ],
65
65
  isError: true,
66
66
  };
67
+ case ErrorCodes.ForbiddenCollscan:
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: error.message,
73
+ },
74
+ ],
75
+ isError: true,
76
+ };
67
77
  }
68
78
  }
69
79
 
@@ -3,6 +3,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { EJSON } from "bson";
6
+ import { checkIndexUsage } from "../../../helpers/indexCheck.js";
6
7
 
7
8
  export const AggregateArgs = {
8
9
  pipeline: z.array(z.record(z.string(), z.unknown())).describe("An array of aggregation stages to execute"),
@@ -23,6 +24,16 @@ export class AggregateTool extends MongoDBToolBase {
23
24
  pipeline,
24
25
  }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
25
26
  const provider = await this.ensureConnected();
27
+
28
+ // Check if aggregate operation uses an index if enabled
29
+ if (this.config.indexCheck) {
30
+ await checkIndexUsage(provider, database, collection, "aggregate", async () => {
31
+ return provider
32
+ .aggregate(database, collection, pipeline, {}, { writeConcern: undefined })
33
+ .explain("queryPlanner");
34
+ });
35
+ }
36
+
26
37
  const documents = await provider.aggregate(database, collection, pipeline).toArray();
27
38
 
28
39
  const content: Array<{ text: string; type: "text" }> = [
@@ -2,6 +2,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
3
3
  import { ToolArgs, OperationType } from "../../tool.js";
4
4
  import { z } from "zod";
5
+ import { checkIndexUsage } from "../../../helpers/indexCheck.js";
5
6
 
6
7
  export const CountArgs = {
7
8
  query: z
@@ -25,6 +26,20 @@ export class CountTool extends MongoDBToolBase {
25
26
 
26
27
  protected async execute({ database, collection, query }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
27
28
  const provider = await this.ensureConnected();
29
+
30
+ // Check if count operation uses an index if enabled
31
+ if (this.config.indexCheck) {
32
+ await checkIndexUsage(provider, database, collection, "count", async () => {
33
+ return provider.runCommandWithCheck(database, {
34
+ explain: {
35
+ count: collection,
36
+ query,
37
+ },
38
+ verbosity: "queryPlanner",
39
+ });
40
+ });
41
+ }
42
+
28
43
  const count = await provider.count(database, collection, query);
29
44
 
30
45
  return {
@@ -4,6 +4,7 @@ import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
5
  import { SortDirection } from "mongodb";
6
6
  import { EJSON } from "bson";
7
+ import { checkIndexUsage } from "../../../helpers/indexCheck.js";
7
8
 
8
9
  export const FindArgs = {
9
10
  filter: z
@@ -39,6 +40,14 @@ export class FindTool extends MongoDBToolBase {
39
40
  sort,
40
41
  }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
41
42
  const provider = await this.ensureConnected();
43
+
44
+ // Check if find operation uses an index if enabled
45
+ if (this.config.indexCheck) {
46
+ await checkIndexUsage(provider, database, collection, "find", async () => {
47
+ return provider.find(database, collection, filter, { projection, limit, sort }).explain("queryPlanner");
48
+ });
49
+ }
50
+
42
51
  const documents = await provider.find(database, collection, filter, { projection, limit, sort }).toArray();
43
52
 
44
53
  const content: Array<{ text: string; type: "text" }> = [
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
4
4
  import { ToolArgs, OperationType } from "../../tool.js";
5
+ import { checkIndexUsage } from "../../../helpers/indexCheck.js";
5
6
 
6
7
  export class UpdateManyTool extends MongoDBToolBase {
7
8
  protected name = "update-many";
@@ -32,6 +33,27 @@ export class UpdateManyTool extends MongoDBToolBase {
32
33
  upsert,
33
34
  }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
34
35
  const provider = await this.ensureConnected();
36
+
37
+ // Check if update operation uses an index if enabled
38
+ if (this.config.indexCheck) {
39
+ await checkIndexUsage(provider, database, collection, "updateMany", async () => {
40
+ return provider.runCommandWithCheck(database, {
41
+ explain: {
42
+ update: collection,
43
+ updates: [
44
+ {
45
+ q: filter || {},
46
+ u: update,
47
+ upsert: upsert || false,
48
+ multi: true,
49
+ },
50
+ ],
51
+ },
52
+ verbosity: "queryPlanner",
53
+ });
54
+ });
55
+ }
56
+
35
57
  const result = await provider.updateMany(database, collection, filter, update, {
36
58
  upsert,
37
59
  });
package/src/tools/tool.ts CHANGED
@@ -74,12 +74,12 @@ export abstract class ToolBase {
74
74
  logger.debug(LogId.toolExecute, "tool", `Executing tool ${this.name}`);
75
75
 
76
76
  const result = await this.execute(...args);
77
- this.emitToolEvent(startTime, result, ...args);
77
+ await this.emitToolEvent(startTime, result, ...args).catch(() => {});
78
78
  return result;
79
79
  } catch (error: unknown) {
80
80
  logger.error(LogId.toolExecuteFailure, "tool", `Error executing ${this.name}: ${error as string}`);
81
81
  const toolResult = await this.handleError(error, args[0] as ToolArgs<typeof this.argsShape>);
82
- this.emitToolEvent(startTime, toolResult, ...args);
82
+ await this.emitToolEvent(startTime, toolResult, ...args).catch(() => {});
83
83
  return toolResult;
84
84
  }
85
85
  };
@@ -179,11 +179,11 @@ export abstract class ToolBase {
179
179
  * @param result - Whether the command succeeded or failed
180
180
  * @param args - The arguments passed to the tool
181
181
  */
182
- private emitToolEvent(
182
+ private async emitToolEvent(
183
183
  startTime: number,
184
184
  result: CallToolResult,
185
185
  ...args: Parameters<ToolCallback<typeof this.argsShape>>
186
- ): void {
186
+ ): Promise<void> {
187
187
  if (!this.telemetry.isTelemetryEnabled()) {
188
188
  return;
189
189
  }
@@ -209,6 +209,6 @@ export abstract class ToolBase {
209
209
  event.properties.project_id = metadata.projectId;
210
210
  }
211
211
 
212
- this.telemetry.emitEvents([event]);
212
+ await this.telemetry.emitEvents([event]);
213
213
  }
214
214
  }