Skip to content

Commit ab608c8

Browse files
committed
wip
1 parent 938342d commit ab608c8

File tree

4 files changed

+95
-31
lines changed

4 files changed

+95
-31
lines changed

src/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const LogId = {
1717
telemetryEmitFailure: mongoLogId(1_002_002),
1818
telemetryEmitStart: mongoLogId(1_002_003),
1919
telemetryEmitSuccess: mongoLogId(1_002_004),
20+
telmetryMetadataError: mongoLogId(1_002_005),
2021

2122
toolExecute: mongoLogId(1_003_001),
2223
toolExecuteFailure: mongoLogId(1_003_002),

src/telemetry/telemetry.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ export class Telemetry {
9797
const result = await this.sendEvents(this.session.apiClient, allEvents);
9898
if (result.success) {
9999
this.eventCache.clearEvents();
100-
logger.debug(LogId.telemetryEmitSuccess, "telemetry", `Sent ${allEvents.length} events successfully`);
100+
logger.debug(
101+
LogId.telemetryEmitSuccess,
102+
"telemetry",
103+
`Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
104+
);
101105
return;
102106
}
103107

src/tools/mongodb/mongodbTool.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { z } from "zod";
2-
import { ToolArgs, ToolBase, ToolCategory } from "../tool.js";
2+
import { ToolArgs, ToolBase, ToolCategory, ToolMetadata } from "../tool.js";
33
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
4-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4+
import { CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js";
55
import { ErrorCodes, MongoDBError } from "../../errors.js";
66
import logger, { LogId } from "../../logger.js";
7+
import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
78

89
export const DbOperationArgs = {
910
database: z.string().describe("Database name"),
@@ -73,4 +74,15 @@ export abstract class MongoDBToolBase extends ToolBase {
7374
protected connectToMongoDB(connectionString: string): Promise<void> {
7475
return this.session.connectToMongoDB(connectionString, this.config.connectOptions);
7576
}
77+
78+
protected resolveToolMetadata(args: { [x: string]: any; }, extra: RequestHandlerExtra<ServerRequest, ServerNotification>): ToolMetadata {
79+
const metadata = super.resolveToolMetadata(args, extra);
80+
81+
// Add projectId to the metadata if running a MongoDB operation to an Atlas cluster
82+
if (this.session.connectedAtlasCluster?.projectId) {
83+
metadata.projectId = this.session.connectedAtlasCluster.projectId;
84+
}
85+
86+
return metadata;
87+
}
7688
}

src/tools/tool.ts

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import { Session } from "../session.js";
55
import logger, { LogId } from "../logger.js";
66
import { Telemetry, isTelemetryEnabled } from "../telemetry/telemetry.js";
7-
import { type ToolEvent } from "../telemetry/types.js";
7+
import { type ToolEvent} from "../telemetry/types.js";
88
import { UserConfig } from "../config.js";
99

1010
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
1111

1212
export type OperationType = "metadata" | "read" | "create" | "delete" | "update";
1313
export type ToolCategory = "mongodb" | "atlas";
14+
export type ToolMetadata = {
15+
projectId?: string;
16+
orgId?: string;
17+
}
1418

1519
export abstract class ToolBase {
1620
protected abstract name: string;
@@ -23,6 +27,7 @@ export abstract class ToolBase {
2327

2428
protected abstract argsShape: ZodRawShape;
2529

30+
2631
protected abstract execute(...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<CallToolResult>;
2732

2833
constructor(
@@ -31,31 +36,6 @@ export abstract class ToolBase {
3136
protected readonly telemetry: Telemetry
3237
) {}
3338

34-
/**
35-
* Creates and emits a tool telemetry event
36-
* @param startTime - Start time in milliseconds
37-
* @param result - Whether the command succeeded or failed
38-
* @param error - Optional error if the command failed
39-
*/
40-
private async emitToolEvent(startTime: number, result: CallToolResult): Promise<void> {
41-
if (!isTelemetryEnabled()) {
42-
return;
43-
}
44-
const duration = Date.now() - startTime;
45-
const event: ToolEvent = {
46-
timestamp: new Date().toISOString(),
47-
source: "mdbmcp",
48-
properties: {
49-
command: this.name,
50-
category: this.category,
51-
component: "tool",
52-
duration_ms: duration,
53-
result: result.isError ? "failure" : "success",
54-
},
55-
};
56-
await this.telemetry.emitEvents([event]);
57-
}
58-
5939
public register(server: McpServer): void {
6040
if (!this.verifyAllowed()) {
6141
return;
@@ -67,12 +47,12 @@ export abstract class ToolBase {
6747
logger.debug(LogId.toolExecute, "tool", `Executing ${this.name} with args: ${JSON.stringify(args)}`);
6848

6949
const result = await this.execute(...args);
70-
await this.emitToolEvent(startTime, result);
50+
await this.emitToolEvent(startTime, result, ...args);
7151
return result;
7252
} catch (error: unknown) {
7353
logger.error(LogId.toolExecuteFailure, "tool", `Error executing ${this.name}: ${error as string}`);
7454
const toolResult = await this.handleError(error, args[0] as ToolArgs<typeof this.argsShape>);
75-
await this.emitToolEvent(startTime, toolResult).catch(() => {});
55+
await this.emitToolEvent(startTime, toolResult, ...args).catch(() => {});
7656
return toolResult;
7757
}
7858
};
@@ -152,4 +132,71 @@ export abstract class ToolBase {
152132
],
153133
};
154134
}
135+
136+
137+
/**
138+
*
139+
* Resolves the tool metadata from the arguments passed to the tool
140+
*
141+
* @param args - The arguments passed to the tool
142+
* @returns The tool metadata
143+
*/
144+
protected resolveToolMetadata(
145+
...args: Parameters<ToolCallback<typeof this.argsShape>>
146+
): ToolMetadata {
147+
const toolMetadata: ToolMetadata = {};
148+
try {
149+
// Parse the arguments to extract project_id and org_id
150+
const argsShape = z.object(this.argsShape);
151+
const parsedArgs = argsShape.safeParse(args[0]);
152+
if (parsedArgs.success && parsedArgs.data?.projectId) {
153+
toolMetadata.projectId = parsedArgs.data?.projectId;
154+
}
155+
156+
if (parsedArgs.success && parsedArgs.data?.orgId) {
157+
toolMetadata.orgId = parsedArgs.data?.orgId;
158+
}
159+
}
160+
catch (error) {
161+
logger.info(LogId.telmetryMetadataError, "tool", `Error resolving tool metadata: ${error as string}`);
162+
}
163+
return toolMetadata;
164+
}
165+
166+
167+
/**
168+
* Creates and emits a tool telemetry event
169+
* @param startTime - Start time in milliseconds
170+
* @param result - Whether the command succeeded or failed
171+
* @param args - The arguments passed to the tool
172+
*/
173+
private async emitToolEvent(startTime: number, result: CallToolResult, ...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<void> {
174+
if (!isTelemetryEnabled()) {
175+
return;
176+
}
177+
const duration = Date.now() - startTime;
178+
const metadata = this.resolveToolMetadata(...args);
179+
const event: ToolEvent = {
180+
timestamp: new Date().toISOString(),
181+
source: "mdbmcp",
182+
properties: {
183+
...this.telemetry.getCommonProperties(),
184+
command: this.name,
185+
category: this.category,
186+
component: "tool",
187+
duration_ms: duration,
188+
result: result.isError ? "failure" : "success",
189+
},
190+
};
191+
192+
if (metadata?.orgId) {
193+
event.properties.org_id = metadata.orgId;
194+
}
195+
196+
if (metadata?.projectId) {
197+
event.properties.project_id = metadata.projectId;
198+
}
199+
200+
await this.telemetry.emitEvents([event]);
201+
}
155202
}

0 commit comments

Comments
 (0)