Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/code-health-long-running.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ permissions: {}
jobs:
run-long-running-tests:
name: Run long running tests
if: github.event_name == 'push' || (github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
Expand Down
28 changes: 23 additions & 5 deletions src/telemetry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type TelemetryEvent<T> = {
duration_ms: number;
result: TelemetryResult;
category: string;
};
} & Record<string, string | number | string[]>;
};

export type BaseEvent = TelemetryEvent<unknown>;
Expand All @@ -28,14 +28,12 @@ export type ToolEventProperties = {
command: string;
error_code?: string;
error_type?: string;
project_id?: string;
org_id?: string;
cluster_name?: string;
is_atlas?: boolean;
atlas_local_deployment_id?: string;
};
} & TelemetryToolMetadata;

export type ToolEvent = TelemetryEvent<ToolEventProperties>;

/**
* Interface for server events
*/
Expand Down Expand Up @@ -137,3 +135,23 @@ export type CommonProperties = {
*/
hosting_mode?: string;
} & CommonStaticProperties;

/**
* Telemetry metadata that can be provided by tools when emitting telemetry events.
* For MongoDB tools, this is typically empty, while for Atlas tools, this should include
* the project and organization IDs if available.
*/
export type TelemetryToolMetadata = AtlasLocalToolMetadata | AtlasToolMetadata | PerfAdvisorToolMetadata;

export type AtlasLocalToolMetadata = {
atlas_local_deployment_id?: string;
};

export type AtlasToolMetadata = {
project_id?: string;
org_id?: string;
};

export type PerfAdvisorToolMetadata = AtlasToolMetadata & {
operations: string[];
};
11 changes: 6 additions & 5 deletions src/tools/atlas/atlasTool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ToolBase, type ToolArgs, type ToolCategory, type TelemetryToolMetadata } from "../tool.js";
import type { AtlasToolMetadata } from "../../telemetry/types.js";
import { ToolBase, type ToolArgs, type ToolCategory } from "../tool.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { LogId } from "../../common/logger.js";
import { z } from "zod";
Expand Down Expand Up @@ -84,8 +85,8 @@ For more information on Atlas API access roles, visit: https://www.mongodb.com/d
protected resolveTelemetryMetadata(
result: CallToolResult,
...args: Parameters<ToolCallback<typeof this.argsShape>>
): TelemetryToolMetadata {
const toolMetadata: TelemetryToolMetadata = {};
): AtlasToolMetadata {
const toolMetadata: AtlasToolMetadata = {};
if (!args.length) {
return toolMetadata;
}
Expand All @@ -107,12 +108,12 @@ For more information on Atlas API access roles, visit: https://www.mongodb.com/d

// Extract projectId using type guard
if ("projectId" in data && typeof data.projectId === "string" && data.projectId.trim() !== "") {
toolMetadata.projectId = data.projectId;
toolMetadata.project_id = data.projectId;
}

// Extract orgId using type guard
if ("orgId" in data && typeof data.orgId === "string" && data.orgId.trim() !== "") {
toolMetadata.orgId = data.orgId;
toolMetadata.org_id = data.orgId;
}
return toolMetadata;
}
Expand Down
15 changes: 14 additions & 1 deletion src/tools/atlas/read/getPerformanceAdvisor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { AtlasToolBase } from "../atlasTool.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js";
import type { OperationType, ToolArgs } from "../../tool.js";
import { formatUntrustedData } from "../../tool.js";
import {
Expand All @@ -13,6 +13,8 @@ import {
SLOW_QUERY_LOGS_COPY,
} from "../../../common/atlas/performanceAdvisorUtils.js";
import { AtlasArgs } from "../../args.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import type { PerfAdvisorToolMetadata } from "../../../telemetry/types.js";

const PerformanceAdvisorOperationType = z.enum([
"suggestedIndexes",
Expand Down Expand Up @@ -130,4 +132,15 @@ export class GetPerformanceAdvisorTool extends AtlasToolBase {
};
}
}

protected override resolveTelemetryMetadata(
result: CallToolResult,
args: ToolArgs<typeof this.argsShape>,
extra: RequestHandlerExtra<ServerRequest, ServerNotification>
): PerfAdvisorToolMetadata {
return {
...super.resolveTelemetryMetadata(result, args, extra),
operations: args.operations,
};
}
}
9 changes: 5 additions & 4 deletions src/tools/atlasLocal/atlasLocalTool.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { TelemetryToolMetadata, ToolArgs, ToolCategory } from "../tool.js";
import type { ToolArgs, ToolCategory } from "../tool.js";
import { ToolBase } from "../tool.js";
import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Client } from "@mongodb-js/atlas-local";
import { LogId } from "../../common/logger.js";
import type { AtlasLocalToolMetadata } from "../../telemetry/types.js";

export const AtlasLocalToolMetadataDeploymentIdKey = "deploymentId";

Expand Down Expand Up @@ -118,14 +119,14 @@ please log a ticket here: https://github.com/mongodb-js/mongodb-mcp-server/issue
return super.handleError(error, args);
}

protected resolveTelemetryMetadata(result: CallToolResult): TelemetryToolMetadata {
const toolMetadata: TelemetryToolMetadata = {};
protected resolveTelemetryMetadata(result: CallToolResult): AtlasLocalToolMetadata {
const toolMetadata: AtlasLocalToolMetadata = {};

// Atlas Local tools set the deployment ID in the result metadata for telemetry
// If the deployment ID is set, we use it for telemetry
const resultDeploymentId = result._meta?.[AtlasLocalToolMetadataDeploymentIdKey];
if (resultDeploymentId !== undefined && typeof resultDeploymentId === "string") {
toolMetadata.atlasLocaldeploymentId = resultDeploymentId;
toolMetadata.atlas_local_deployment_id = resultDeploymentId;
}

return toolMetadata;
Expand Down
9 changes: 5 additions & 4 deletions src/tools/mongodb/mongodbTool.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { z } from "zod";
import type { ToolArgs, ToolCategory, TelemetryToolMetadata } from "../tool.js";
import type { ToolArgs, ToolCategory } from "../tool.js";
import { ToolBase } from "../tool.js";
import type { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { ErrorCodes, MongoDBError } from "../../common/errors.js";
import { LogId } from "../../common/logger.js";
import type { Server } from "../../server.js";
import type { AtlasToolMetadata } from "../../telemetry/types.js";

export const DbOperationArgs = {
database: z.string().describe("Database name"),
Expand Down Expand Up @@ -115,12 +116,12 @@ export abstract class MongoDBToolBase extends ToolBase {
result: CallToolResult,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
args: ToolArgs<typeof this.argsShape>
): TelemetryToolMetadata {
const metadata: TelemetryToolMetadata = {};
): AtlasToolMetadata {
const metadata: AtlasToolMetadata = {};

// Add projectId to the metadata if running a MongoDB operation to an Atlas cluster
if (this.session.connectedAtlasCluster?.projectId) {
metadata.projectId = this.session.connectedAtlasCluster.projectId;
metadata.project_id = this.session.connectedAtlasCluster.projectId;
}

return metadata;
Expand Down
26 changes: 2 additions & 24 deletions src/tools/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/
import type { Session } from "../common/session.js";
import { LogId } from "../common/logger.js";
import type { Telemetry } from "../telemetry/telemetry.js";
import { type ToolEvent } from "../telemetry/types.js";
import type { TelemetryToolMetadata, ToolEvent } from "../telemetry/types.js";
import type { UserConfig } from "../common/config.js";
import type { Server } from "../server.js";
import type { Elicitation } from "../elicitation.js";
Expand Down Expand Up @@ -39,17 +39,6 @@ export type OperationType = "metadata" | "read" | "create" | "delete" | "update"
*/
export type ToolCategory = "mongodb" | "atlas" | "atlas-local";

/**
* Telemetry metadata that can be provided by tools when emitting telemetry events.
* For MongoDB tools, this is typically empty, while for Atlas tools, this should include
* the project and organization IDs if available.
*/
export type TelemetryToolMetadata = {
projectId?: string;
orgId?: string;
atlasLocaldeploymentId?: string;
};

export type ToolConstructorParams = {
session: Session;
config: UserConfig;
Expand Down Expand Up @@ -304,21 +293,10 @@ export abstract class ToolBase {
component: "tool",
duration_ms: duration,
result: result.isError ? "failure" : "success",
...metadata,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice thanks!

},
};

if (metadata?.orgId) {
event.properties.org_id = metadata.orgId;
}

if (metadata?.projectId) {
event.properties.project_id = metadata.projectId;
}

if (metadata?.atlasLocaldeploymentId) {
event.properties.atlas_local_deployment_id = metadata.atlasLocaldeploymentId;
}

this.telemetry.emitEvents([event]);
}

Expand Down
Loading
Loading