From 2271809fca9afb37d92fc26206a5113774939276 Mon Sep 17 00:00:00 2001 From: nirinchev Date: Wed, 29 Oct 2025 14:19:18 +0100 Subject: [PATCH 1/3] chore: include tool args for perf advisor telemetry --- src/telemetry/types.ts | 2 +- src/tools/atlas/atlasTool.ts | 4 +- src/tools/atlas/read/getPerformanceAdvisor.ts | 15 +- src/tools/atlasLocal/atlasLocalTool.ts | 2 +- src/tools/mongodb/mongodbTool.ts | 2 +- src/tools/tool.ts | 21 +- .../tools/atlas/performanceAdvisor.test.ts | 257 ++++++++++++------ tests/unit/toolBase.test.ts | 69 ++++- 8 files changed, 255 insertions(+), 117 deletions(-) diff --git a/src/telemetry/types.ts b/src/telemetry/types.ts index f1cfa06cb..a31851120 100644 --- a/src/telemetry/types.ts +++ b/src/telemetry/types.ts @@ -16,7 +16,7 @@ export type TelemetryEvent = { duration_ms: number; result: TelemetryResult; category: string; - }; + } & Record; }; export type BaseEvent = TelemetryEvent; diff --git a/src/tools/atlas/atlasTool.ts b/src/tools/atlas/atlasTool.ts index a83bfb1de..d9a7f4b81 100644 --- a/src/tools/atlas/atlasTool.ts +++ b/src/tools/atlas/atlasTool.ts @@ -107,12 +107,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; } diff --git a/src/tools/atlas/read/getPerformanceAdvisor.ts b/src/tools/atlas/read/getPerformanceAdvisor.ts index c7aead5c4..18b92ebb6 100644 --- a/src/tools/atlas/read/getPerformanceAdvisor.ts +++ b/src/tools/atlas/read/getPerformanceAdvisor.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { AtlasToolBase } from "../atlasTool.js"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import type { OperationType, ToolArgs } from "../../tool.js"; +import type { CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js"; +import type { OperationType, TelemetryToolMetadata, ToolArgs } from "../../tool.js"; import { formatUntrustedData } from "../../tool.js"; import { getSuggestedIndexes, @@ -13,6 +13,7 @@ import { SLOW_QUERY_LOGS_COPY, } from "../../../common/atlas/performanceAdvisorUtils.js"; import { AtlasArgs } from "../../args.js"; +import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; const PerformanceAdvisorOperationType = z.enum([ "suggestedIndexes", @@ -130,4 +131,14 @@ export class GetPerformanceAdvisorTool extends AtlasToolBase { }; } } + + protected override resolveTelemetryMetadata( + result: CallToolResult, + args: ToolArgs, + extra: RequestHandlerExtra + ): TelemetryToolMetadata { + const baseMetadata = super.resolveTelemetryMetadata(result, args, extra); + baseMetadata.operations = args.operations; + return baseMetadata; + } } diff --git a/src/tools/atlasLocal/atlasLocalTool.ts b/src/tools/atlasLocal/atlasLocalTool.ts index 266dd3e4d..b6ce10b96 100644 --- a/src/tools/atlasLocal/atlasLocalTool.ts +++ b/src/tools/atlasLocal/atlasLocalTool.ts @@ -125,7 +125,7 @@ please log a ticket here: https://github.com/mongodb-js/mongodb-mcp-server/issue // 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; diff --git a/src/tools/mongodb/mongodbTool.ts b/src/tools/mongodb/mongodbTool.ts index beb278d5b..4f4d6b469 100644 --- a/src/tools/mongodb/mongodbTool.ts +++ b/src/tools/mongodb/mongodbTool.ts @@ -120,7 +120,7 @@ export abstract class MongoDBToolBase extends ToolBase { // 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; diff --git a/src/tools/tool.ts b/src/tools/tool.ts index ec9f01a61..0d73896e6 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -44,10 +44,10 @@ export type ToolCategory = "mongodb" | "atlas" | "atlas-local"; * the project and organization IDs if available. */ export type TelemetryToolMetadata = { - projectId?: string; - orgId?: string; - atlasLocaldeploymentId?: string; -}; + project_id?: string; + org_id?: string; + atlas_local_deployment_id?: string; +} & Record; export type ToolConstructorParams = { session: Session; @@ -303,21 +303,10 @@ export abstract class ToolBase { component: "tool", duration_ms: duration, result: result.isError ? "failure" : "success", + ...metadata, }, }; - 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]); } diff --git a/tests/integration/tools/atlas/performanceAdvisor.test.ts b/tests/integration/tools/atlas/performanceAdvisor.test.ts index bb3753485..253fffbb7 100644 --- a/tests/integration/tools/atlas/performanceAdvisor.test.ts +++ b/tests/integration/tools/atlas/performanceAdvisor.test.ts @@ -1,10 +1,20 @@ // This test file includes long running tests (>10 minutes) because we provision a real M10 cluster, which can take up to 10 minutes to provision. // The timeouts for the beforeAll/afterAll hooks have been modified to account for longer running tests. +import { ObjectId } from "bson"; import type { Session } from "../../../../src/common/session.js"; -import { DEFAULT_LONG_RUNNING_TEST_WAIT_TIMEOUT_MS, expectDefined, getResponseElements } from "../../helpers.js"; +import { + DEFAULT_LONG_RUNNING_TEST_WAIT_TIMEOUT_MS, + defaultDriverOptions, + defaultTestConfig, + expectDefined, + getResponseElements, + setupIntegrationTest, +} from "../../helpers.js"; import { describeWithAtlas, withProject, randomId, waitCluster, deleteCluster } from "./atlasHelpers.js"; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import type { Mock, MockInstance } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { BaseEvent, ToolEvent } from "../../../../src/telemetry/types.js"; describeWithAtlas("performanceAdvisor", (integration) => { withProject(integration, ({ getProjectId }) => { @@ -116,116 +126,183 @@ describeWithAtlas("performanceAdvisor", (integration) => { expect(elements[1]?.text).toContain("## Drop Index Suggestions"); expect(elements[1]?.text).toContain("## Schema Suggestions"); }); + }); + }); +}); - it("returns mocked performance advisor data", async () => { - const projectId = getProjectId(); - const session = integration.mcpServer().session; +describe("mocked atlas-get-performance-advisor", () => { + const integration = setupIntegrationTest( + () => ({ + ...defaultTestConfig, + apiClientId: process.env.MDB_MCP_API_CLIENT_ID || "test-client", + apiClientSecret: process.env.MDB_MCP_API_CLIENT_SECRET || "test-secret", + apiBaseUrl: process.env.MDB_MCP_API_BASE_URL ?? "https://cloud-dev.mongodb.com", + }), + () => defaultDriverOptions + ); - // Mock the API client methods since we can't guarantee performance advisor data - const mockSuggestedIndexes = vi.fn().mockResolvedValue({ - content: { - suggestedIndexes: [ - { - namespace: "testdb.testcollection", - index: { field: 1 }, - impact: ["queryShapeString"], - }, - ], + let mockEmitEvents: MockInstance<(events: BaseEvent[]) => void>; + let projectId: string; + + let mockSuggestedIndexes: Mock; + let mockDropIndexSuggestions: Mock; + let mockSchemaAdvice: Mock; + let mockSlowQueries: Mock; + let mockGetCluster: Mock; + + beforeEach(() => { + mockEmitEvents = vi.spyOn(integration.mcpServer()["telemetry"], "emitEvents"); + vi.spyOn(integration.mcpServer()["telemetry"], "isTelemetryEnabled").mockReturnValue(true); + + projectId = new ObjectId().toString(); + + const session = integration.mcpServer().session; + + // Mock the API client methods since we can't guarantee performance advisor data + mockSuggestedIndexes = vi.fn().mockResolvedValue({ + content: { + suggestedIndexes: [ + { + namespace: "testdb.testcollection", + index: { field: 1 }, + impact: ["queryShapeString"], }, - }); + ], + }, + }); - const mockDropIndexSuggestions = vi.fn().mockResolvedValue({ - content: { - hiddenIndexes: [], - redundantIndexes: [ - { - accessCount: 100, - namespace: "testdb.testcollection", - index: { field: 1 }, - reason: "Redundant with compound index", - }, - ], - unusedIndexes: [], + mockDropIndexSuggestions = vi.fn().mockResolvedValue({ + content: { + hiddenIndexes: [], + redundantIndexes: [ + { + accessCount: 100, + namespace: "testdb.testcollection", + index: { field: 1 }, + reason: "Redundant with compound index", }, - }); + ], + unusedIndexes: [], + }, + }); - const mockSchemaAdvice = vi.fn().mockResolvedValue({ - content: { - recommendations: [ + mockSchemaAdvice = vi.fn().mockResolvedValue({ + content: { + recommendations: [ + { + description: "Consider adding an index on 'status' field", + recommendation: "REDUCE_LOOKUP_OPS", + affectedNamespaces: [ { - description: "Consider adding an index on 'status' field", - recommendation: "REDUCE_LOOKUP_OPS", - affectedNamespaces: [ + namespace: "testdb.testcollection", + triggers: [ { - namespace: "testdb.testcollection", - triggers: [ - { - triggerType: "PERCENT_QUERIES_USE_LOOKUP", - details: - "Queries filtering by status field are causing collection scans", - }, - ], + triggerType: "PERCENT_QUERIES_USE_LOOKUP", + details: "Queries filtering by status field are causing collection scans", }, ], }, ], }, - }); + ], + }, + }); - const mockSlowQueries = vi.fn().mockResolvedValue({ - slowQueries: [ - { - namespace: "testdb.testcollection", - query: { find: "testcollection", filter: { status: "active" } }, - duration: 1500, - timestamp: "2024-01-15T10:30:00Z", - }, - ], - }); + mockSlowQueries = vi.fn().mockResolvedValue({ + slowQueries: [ + { + namespace: "testdb.testcollection", + query: { find: "testcollection", filter: { status: "active" } }, + duration: 1500, + timestamp: "2024-01-15T10:30:00Z", + }, + ], + }); - const mockGetCluster = vi.fn().mockResolvedValue({ - connectionStrings: { - standard: "mongodb://test-cluster.mongodb.net:27017", - }, - }); + mockGetCluster = vi.fn().mockResolvedValue({ + connectionStrings: { + standard: "mongodb://test-cluster.mongodb.net:27017", + }, + }); - session.apiClient.listClusterSuggestedIndexes = mockSuggestedIndexes; - session.apiClient.listDropIndexes = mockDropIndexSuggestions; - session.apiClient.listSchemaAdvice = mockSchemaAdvice; - session.apiClient.listSlowQueries = mockSlowQueries; - session.apiClient.getCluster = mockGetCluster; + session.apiClient.listClusterSuggestedIndexes = mockSuggestedIndexes; + session.apiClient.listDropIndexes = mockDropIndexSuggestions; + session.apiClient.listSchemaAdvice = mockSchemaAdvice; + session.apiClient.listSlowQueries = mockSlowQueries; + session.apiClient.getCluster = mockGetCluster; + }); - const response = await integration.mcpClient().callTool({ - name: "atlas-get-performance-advisor", - arguments: { - projectId, - clusterName: "mockClusterName", - operations: ["suggestedIndexes", "dropIndexSuggestions", "slowQueryLogs", "schemaSuggestions"], - }, - }); + afterEach(() => { + vi.clearAllMocks(); + }); - if (response.isError) { - console.error("Performance advisor call failed:", response.content); - throw new Error("Performance advisor call failed - see console for details"); - } + it("returns mocked performance advisor data", async () => { + const response = await integration.mcpClient().callTool({ + name: "atlas-get-performance-advisor", + arguments: { + projectId, + clusterName: "mockClusterName", + operations: ["suggestedIndexes", "dropIndexSuggestions", "slowQueryLogs", "schemaSuggestions"], + }, + }); - const elements = getResponseElements(response.content); - expect(elements).toHaveLength(2); + expect(response.isError).toBe(false); - expect(elements[0]?.text).toContain("Performance advisor data"); - expect(elements[1]?.text).toContain(" { + await integration.mcpClient().callTool({ + name: "atlas-get-performance-advisor", + arguments: { + projectId, + clusterName: "mockClusterName", + }, }); + + expect(mockEmitEvents).toHaveBeenCalled(); + const emittedEvent = mockEmitEvents.mock.lastCall?.[0][0] as ToolEvent; + expectDefined(emittedEvent); + expect(emittedEvent.properties.result).toEqual("success"); + expect(emittedEvent.properties.command).toEqual("atlas-get-performance-advisor"); + expect(emittedEvent.properties.operations).toEqual([ + "suggestedIndexes", + "dropIndexSuggestions", + "slowQueryLogs", + "schemaSuggestions", + ]); + }); + + it("emits operations when supplied", async () => { + await integration.mcpClient().callTool({ + name: "atlas-get-performance-advisor", + arguments: { + projectId, + clusterName: "mockClusterName", + operations: ["suggestedIndexes", "slowQueryLogs"], + }, + }); + + expect(mockEmitEvents).toHaveBeenCalled(); + const emittedEvent = mockEmitEvents.mock.lastCall?.[0][0] as ToolEvent; + expectDefined(emittedEvent); + expect(emittedEvent.properties.result).toEqual("success"); + expect(emittedEvent.properties.command).toEqual("atlas-get-performance-advisor"); + expect(emittedEvent.properties.operations).toEqual(["suggestedIndexes", "slowQueryLogs"]); }); }); diff --git a/tests/unit/toolBase.test.ts b/tests/unit/toolBase.test.ts index 984aa5bfb..1ed1d707f 100644 --- a/tests/unit/toolBase.test.ts +++ b/tests/unit/toolBase.test.ts @@ -1,13 +1,19 @@ +import type { Mock } from "vitest"; import { describe, it, expect, vi, beforeEach, type MockedFunction } from "vitest"; +import type { ZodRawShape } from "zod"; import { z } from "zod"; import { ToolBase, type OperationType, type ToolCategory, type ToolConstructorParams } from "../../src/tools/tool.js"; -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js"; import type { Session } from "../../src/common/session.js"; import type { PreviewFeature, UserConfig } from "../../src/common/config.js"; import type { Telemetry } from "../../src/telemetry/telemetry.js"; import type { Elicitation } from "../../src/elicitation.js"; import type { CompositeLogger } from "../../src/common/logger.js"; -import type { TelemetryToolMetadata, ToolCallbackArgs } from "../../src/tools/tool.js"; +import type { TelemetryToolMetadata, ToolArgs, ToolCallbackArgs } from "../../src/tools/tool.js"; +import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { Server } from "../../src/server.js"; +import type { ToolEvent } from "../../src/telemetry/types.js"; +import { expectDefined } from "../integration/helpers.js"; describe("ToolBase", () => { let mockSession: Session; @@ -33,9 +39,13 @@ describe("ToolBase", () => { mockConfig = { confirmationRequiredTools: [], previewFeatures: [], + disabledTools: [], } as unknown as UserConfig; - mockTelemetry = {} as Telemetry; + mockTelemetry = { + isTelemetryEnabled: () => true, + emitEvents: vi.fn(), + } as unknown as Telemetry; mockRequestConfirmation = vi.fn(); mockElicitation = { @@ -116,6 +126,48 @@ describe("ToolBase", () => { expect(testTool["isFeatureEnabled"]("anotherFeature" as PreviewFeature)).to.equal(false); }); }); + + describe("resolveTelemetryMetadata", () => { + let mockCallback: ToolCallback<(typeof testTool)["argsShape"]>; + beforeEach(() => { + const mockServer = { + mcpServer: { + tool: ( + name: string, + description: string, + paramsSchema: unknown, + annotations: ToolAnnotations, + cb: ToolCallback + ): void => { + expect(name).toBe(testTool.name); + expect(description).toBe(testTool["description"]); + mockCallback = cb; + }, + }, + }; + testTool.register(mockServer as unknown as Server); + }); + + it("should return empty metadata by default", async () => { + await mockCallback({ param1: "value1" }, {} as never); + const event = ((mockTelemetry.emitEvents as Mock).mock.lastCall?.[0] as ToolEvent[])[0]; + expectDefined(event); + expect(event.properties.result).to.equal("success"); + expect(event.properties).not.toHaveProperty("project_id"); + expect(event.properties).not.toHaveProperty("org_id"); + expect(event.properties).not.toHaveProperty("atlas_local_deployment_id"); + expect(event.properties).not.toHaveProperty("test_param2"); + }); + + it("should include custom telemetry metadata", async () => { + await mockCallback({ param1: "value1", param2: 3 }, {} as never); + const event = ((mockTelemetry.emitEvents as Mock).mock.lastCall?.[0] as ToolEvent[])[0]; + expectDefined(event); + + expect(event.properties.result).to.equal("success"); + expect(event.properties).toHaveProperty("test_param2", "three"); + }); + }); }); class TestTool extends ToolBase { @@ -139,7 +191,16 @@ class TestTool extends ToolBase { }); } - protected resolveTelemetryMetadata(): TelemetryToolMetadata { + protected resolveTelemetryMetadata( + result: CallToolResult, + args: ToolArgs + ): TelemetryToolMetadata { + if (args.param2 === 3) { + return { + test_param2: "three", + }; + } + return {}; } } From 902c24ab0398eeec138aa14bd33135a000ceea80 Mon Sep 17 00:00:00 2001 From: nirinchev Date: Wed, 29 Oct 2025 15:05:19 +0100 Subject: [PATCH 2/3] remove if clause in long running tests --- .github/workflows/code-health-long-running.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/code-health-long-running.yml b/.github/workflows/code-health-long-running.yml index ee94c30ca..2d3823457 100644 --- a/.github/workflows/code-health-long-running.yml +++ b/.github/workflows/code-health-long-running.yml @@ -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 From 118950f3423fc53a20d1aa191721e166d5243ac1 Mon Sep 17 00:00:00 2001 From: nirinchev Date: Tue, 4 Nov 2025 11:55:47 +0100 Subject: [PATCH 3/3] reshuffle the types a bit --- src/telemetry/types.ts | 26 ++++++++++++++++--- src/tools/atlas/atlasTool.ts | 7 ++--- src/tools/atlas/read/getPerformanceAdvisor.ts | 12 +++++---- src/tools/atlasLocal/atlasLocalTool.ts | 7 ++--- src/tools/mongodb/mongodbTool.ts | 7 ++--- src/tools/tool.ts | 13 +--------- 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/telemetry/types.ts b/src/telemetry/types.ts index a31851120..268c74007 100644 --- a/src/telemetry/types.ts +++ b/src/telemetry/types.ts @@ -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; + /** * Interface for server events */ @@ -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[]; +}; diff --git a/src/tools/atlas/atlasTool.ts b/src/tools/atlas/atlasTool.ts index d9a7f4b81..594d832af 100644 --- a/src/tools/atlas/atlasTool.ts +++ b/src/tools/atlas/atlasTool.ts @@ -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"; @@ -84,8 +85,8 @@ For more information on Atlas API access roles, visit: https://www.mongodb.com/d protected resolveTelemetryMetadata( result: CallToolResult, ...args: Parameters> - ): TelemetryToolMetadata { - const toolMetadata: TelemetryToolMetadata = {}; + ): AtlasToolMetadata { + const toolMetadata: AtlasToolMetadata = {}; if (!args.length) { return toolMetadata; } diff --git a/src/tools/atlas/read/getPerformanceAdvisor.ts b/src/tools/atlas/read/getPerformanceAdvisor.ts index 18b92ebb6..f62450106 100644 --- a/src/tools/atlas/read/getPerformanceAdvisor.ts +++ b/src/tools/atlas/read/getPerformanceAdvisor.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { AtlasToolBase } from "../atlasTool.js"; import type { CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js"; -import type { OperationType, TelemetryToolMetadata, ToolArgs } from "../../tool.js"; +import type { OperationType, ToolArgs } from "../../tool.js"; import { formatUntrustedData } from "../../tool.js"; import { getSuggestedIndexes, @@ -14,6 +14,7 @@ import { } 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", @@ -136,9 +137,10 @@ export class GetPerformanceAdvisorTool extends AtlasToolBase { result: CallToolResult, args: ToolArgs, extra: RequestHandlerExtra - ): TelemetryToolMetadata { - const baseMetadata = super.resolveTelemetryMetadata(result, args, extra); - baseMetadata.operations = args.operations; - return baseMetadata; + ): PerfAdvisorToolMetadata { + return { + ...super.resolveTelemetryMetadata(result, args, extra), + operations: args.operations, + }; } } diff --git a/src/tools/atlasLocal/atlasLocalTool.ts b/src/tools/atlasLocal/atlasLocalTool.ts index b6ce10b96..dc548e208 100644 --- a/src/tools/atlasLocal/atlasLocalTool.ts +++ b/src/tools/atlasLocal/atlasLocalTool.ts @@ -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"; @@ -118,8 +119,8 @@ 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 diff --git a/src/tools/mongodb/mongodbTool.ts b/src/tools/mongodb/mongodbTool.ts index 4f4d6b469..735579a7b 100644 --- a/src/tools/mongodb/mongodbTool.ts +++ b/src/tools/mongodb/mongodbTool.ts @@ -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"), @@ -115,8 +116,8 @@ export abstract class MongoDBToolBase extends ToolBase { result: CallToolResult, // eslint-disable-next-line @typescript-eslint/no-unused-vars args: ToolArgs - ): 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) { diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 0d73896e6..52cb47309 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -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 { PreviewFeature, UserConfig } from "../common/config.js"; import type { Server } from "../server.js"; import type { Elicitation } from "../elicitation.js"; @@ -38,17 +38,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 = { - project_id?: string; - org_id?: string; - atlas_local_deployment_id?: string; -} & Record; - export type ToolConstructorParams = { session: Session; config: UserConfig;