-
Notifications
You must be signed in to change notification settings - Fork 36
chore: add orgId and projectid #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
938342d
ab608c8
39a7def
ee23bed
9b675d4
4a764fe
bfd6d3b
dc0e4d3
596c233
36dab1c
72539ab
43e5c75
8721c39
b4ac4db
bbc49be
a06c460
a95af7b
3eee38c
f0142ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,18 @@ | |
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; | ||
import { Session } from "../session.js"; | ||
import logger, { LogId } from "../logger.js"; | ||
import { Telemetry } from "../telemetry/telemetry.js"; | ||
import { Telemetry, isTelemetryEnabled } from "../telemetry/telemetry.js"; | ||
import { type ToolEvent } from "../telemetry/types.js"; | ||
import { UserConfig } from "../config.js"; | ||
|
||
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>; | ||
|
||
export type OperationType = "metadata" | "read" | "create" | "delete" | "update"; | ||
export type ToolCategory = "mongodb" | "atlas"; | ||
export type TelemetryToolMetadata = { | ||
projectId?: string; | ||
orgId?: string; | ||
}; | ||
|
||
export abstract class ToolBase { | ||
protected abstract name: string; | ||
|
@@ -31,28 +35,6 @@ | |
protected readonly telemetry: Telemetry | ||
) {} | ||
|
||
/** | ||
* Creates and emits a tool telemetry event | ||
* @param startTime - Start time in milliseconds | ||
* @param result - Whether the command succeeded or failed | ||
* @param error - Optional error if the command failed | ||
*/ | ||
private async emitToolEvent(startTime: number, result: CallToolResult): Promise<void> { | ||
const duration = Date.now() - startTime; | ||
const event: ToolEvent = { | ||
timestamp: new Date().toISOString(), | ||
source: "mdbmcp", | ||
properties: { | ||
command: this.name, | ||
category: this.category, | ||
component: "tool", | ||
duration_ms: duration, | ||
result: result.isError ? "failure" : "success", | ||
}, | ||
}; | ||
await this.telemetry.emitEvents([event]); | ||
} | ||
|
||
public register(server: McpServer): void { | ||
if (!this.verifyAllowed()) { | ||
return; | ||
|
@@ -64,12 +46,12 @@ | |
logger.debug(LogId.toolExecute, "tool", `Executing ${this.name} with args: ${JSON.stringify(args)}`); | ||
|
||
const result = await this.execute(...args); | ||
await this.emitToolEvent(startTime, result); | ||
await this.emitToolEvent(startTime, result, ...args); | ||
return result; | ||
} catch (error: unknown) { | ||
logger.error(LogId.toolExecuteFailure, "tool", `Error executing ${this.name}: ${error as string}`); | ||
const toolResult = await this.handleError(error, args[0] as ToolArgs<typeof this.argsShape>); | ||
await this.emitToolEvent(startTime, toolResult).catch(() => {}); | ||
await this.emitToolEvent(startTime, toolResult, ...args).catch(() => {}); | ||
return toolResult; | ||
} | ||
}; | ||
|
@@ -123,7 +105,7 @@ | |
if (errorClarification) { | ||
logger.debug( | ||
LogId.toolDisabled, | ||
"tool", | ||
"tool", | ||
`Prevented registration of ${this.name} because ${errorClarification} is disabled in the config` | ||
); | ||
|
||
|
@@ -149,4 +131,89 @@ | |
], | ||
}; | ||
} | ||
|
||
/** | ||
* | ||
* Resolves the tool metadata from the arguments passed to the tool | ||
* | ||
* @param args - The arguments passed to the tool | ||
* @returns The tool metadata | ||
*/ | ||
protected resolveTelemetryMetadata(...args: Parameters<ToolCallback<typeof this.argsShape>>): TelemetryToolMetadata { | ||
const toolMetadata: TelemetryToolMetadata = {}; | ||
try { | ||
if (!args.length) { | ||
return toolMetadata; | ||
} | ||
|
||
// Create a typed parser for the exact shape we expect | ||
const argsShape = z.object(this.argsShape); | ||
const parsedResult = argsShape.safeParse(args[0]); | ||
|
||
if (!parsedResult.success) { | ||
logger.debug( | ||
LogId.telmetryMetadataError, | ||
"tool", | ||
`Error parsing tool arguments: ${parsedResult.error.message}` | ||
); | ||
return toolMetadata; | ||
} | ||
|
||
const data = parsedResult.data; | ||
|
||
// Extract projectId using type guard | ||
if ("projectId" in data && typeof data.projectId === "string" && data.projectId.trim() !== "") { | ||
toolMetadata.projectId = data.projectId; | ||
} | ||
|
||
// Extract orgId using type guard | ||
if ("orgId" in data && typeof data.orgId === "string" && data.orgId.trim() !== "") { | ||
toolMetadata.orgId = data.orgId; | ||
} | ||
} catch (error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [q] when might catch happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hopefully never I just don't want telemetry to cause any issues but can remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see no method that might have side effects, which line might throw? perhaps perhaps the try catch is better suited for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can be! moving There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's just that we could still emit if this goes wrong, but no strong opinions |
||
const errorMessage = error instanceof Error ? error.message : String(error); | ||
logger.debug(LogId.telmetryMetadataError, "tool", `Error resolving tool metadata: ${errorMessage}`); | ||
} | ||
return toolMetadata; | ||
} | ||
|
||
/** | ||
* Creates and emits a tool telemetry event | ||
* @param startTime - Start time in milliseconds | ||
* @param result - Whether the command succeeded or failed | ||
* @param args - The arguments passed to the tool | ||
*/ | ||
private async emitToolEvent( | ||
startTime: number, | ||
result: CallToolResult, | ||
...args: Parameters<ToolCallback<typeof this.argsShape>> | ||
): Promise<void> { | ||
if (!isTelemetryEnabled()) { | ||
gagik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
const duration = Date.now() - startTime; | ||
const metadata = this.resolveTelemetryMetadata(...args); | ||
const event: ToolEvent = { | ||
timestamp: new Date().toISOString(), | ||
source: "mdbmcp", | ||
properties: { | ||
...this.telemetry.getCommonProperties(), | ||
blva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
command: this.name, | ||
category: this.category, | ||
component: "tool", | ||
duration_ms: duration, | ||
result: result.isError ? "failure" : "success", | ||
}, | ||
}; | ||
|
||
if (metadata?.orgId) { | ||
event.properties.org_id = metadata.orgId; | ||
} | ||
|
||
if (metadata?.projectId) { | ||
event.properties.project_id = metadata.projectId; | ||
} | ||
|
||
await this.telemetry.emitEvents([event]); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.