From 86d509d6c14787d7abbe78fb8709960b5c1f7d07 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Wed, 8 Oct 2025 17:10:08 +0200 Subject: [PATCH 1/7] chore: MCP-247 method to detect if a cluster support search indexes --- src/common/session.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/common/session.ts b/src/common/session.ts index 3c702a645..22e3b1b9b 100644 --- a/src/common/session.ts +++ b/src/common/session.ts @@ -153,4 +153,20 @@ export class Session extends EventEmitter { get connectedAtlasCluster(): AtlasClusterConnectionInfo | undefined { return this.connectionManager.currentConnectionState.connectedAtlasCluster; } + + async isSearchIndexSupported(): Promise { + try { + const dummyDatabase = `db-${Date.now()}`; + const dummyCollection = `coll-${Date.now()}`; + // If a cluster supports search indexes, the call below will succeed + // with a cursor otherwise will throw a MongoServerError + await this.serviceProvider.getSearchIndexes(dummyDatabase, dummyCollection); + return true; + } catch (error) { + if (error instanceof Error && "codeName" in error && error.codeName === "SearchNotEnabled") { + return false; + } + throw error; + } + } } From 209a44be0719ce92478809130483cbc6f6a1a389 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Wed, 8 Oct 2025 17:10:48 +0200 Subject: [PATCH 2/7] chore: MCP-248 add search index info in debug resource --- src/resources/common/debug.ts | 8 +++++--- src/resources/resource.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/resources/common/debug.ts b/src/resources/common/debug.ts index ad1f383df..65e1bf377 100644 --- a/src/resources/common/debug.ts +++ b/src/resources/common/debug.ts @@ -56,13 +56,15 @@ export class DebugResource extends ReactiveResource< } } - toOutput(): string { + async toOutput(): Promise { let result = ""; switch (this.current.tag) { - case "connected": - result += "The user is connected to the MongoDB cluster."; + case "connected": { + const searchIndexesSupported = await this.session.isSearchIndexSupported(); + result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes." : ""}.`; break; + } case "errored": result += `The user is not connected to a MongoDB cluster because of an error.\n`; if (this.current.connectedAtlasCluster) { diff --git a/src/resources/resource.ts b/src/resources/resource.ts index cf265a490..a9cb702ac 100644 --- a/src/resources/resource.ts +++ b/src/resources/resource.ts @@ -73,10 +73,10 @@ export abstract class ReactiveResource ({ + private resourceCallback: ReadResourceCallback = async (uri) => ({ contents: [ { - text: this.toOutput(), + text: await this.toOutput(), mimeType: "application/json", uri: uri.href, }, @@ -101,5 +101,5 @@ export abstract class ReactiveResource[]): Value; - public abstract toOutput(): string; + public abstract toOutput(): string | Promise; } From 6549ce15e9cb1a680004d197e9f9e472d70d9e60 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Wed, 8 Oct 2025 17:20:52 +0200 Subject: [PATCH 3/7] chore: tests for isSearchIndexSupported --- src/common/session.ts | 9 +++------ tests/unit/common/session.test.ts | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/common/session.ts b/src/common/session.ts index 22e3b1b9b..9fadf6170 100644 --- a/src/common/session.ts +++ b/src/common/session.ts @@ -159,14 +159,11 @@ export class Session extends EventEmitter { const dummyDatabase = `db-${Date.now()}`; const dummyCollection = `coll-${Date.now()}`; // If a cluster supports search indexes, the call below will succeed - // with a cursor otherwise will throw a MongoServerError + // with a cursor otherwise will throw an Error await this.serviceProvider.getSearchIndexes(dummyDatabase, dummyCollection); return true; - } catch (error) { - if (error instanceof Error && "codeName" in error && error.codeName === "SearchNotEnabled") { - return false; - } - throw error; + } catch { + return false; } } } diff --git a/tests/unit/common/session.test.ts b/tests/unit/common/session.test.ts index 9402df246..ea6ac348b 100644 --- a/tests/unit/common/session.test.ts +++ b/tests/unit/common/session.test.ts @@ -1,4 +1,4 @@ -import type { Mocked } from "vitest"; +import type { Mocked, MockedFunction } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver"; import { Session } from "../../../src/common/session.js"; @@ -119,4 +119,30 @@ describe("Session", () => { expect(connectionString).toContain("--test-device-id--unknown"); }); }); + + describe("isSearchIndexSupported", () => { + let getSearchIndexesMock: MockedFunction<() => unknown>; + beforeEach(() => { + getSearchIndexesMock = vi.fn(); + MockNodeDriverServiceProvider.connect = vi.fn().mockResolvedValue({ + getSearchIndexes: getSearchIndexesMock, + } as unknown as NodeDriverServiceProvider); + }); + + it("should return true if listing search indexes succeed", async () => { + getSearchIndexesMock.mockResolvedValue([]); + await session.connectToMongoDB({ + connectionString: "mongodb://localhost:27017", + }); + expect(await session.isSearchIndexSupported()).toEqual(true); + }); + + it("should return false if listing search indexes fail with search error", async () => { + getSearchIndexesMock.mockRejectedValue(new Error("SearchNotEnabled")); + await session.connectToMongoDB({ + connectionString: "mongodb://localhost:27017", + }); + expect(await session.isSearchIndexSupported()).toEqual(false); + }); + }); }); From 9ea5f6a6a31d86611355220c9aee89c10ed7b8a6 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Thu, 9 Oct 2025 11:20:33 +0200 Subject: [PATCH 4/7] chore: tests for isSearchIndexSupported --- src/resources/common/debug.ts | 2 +- tests/unit/resources/common/debug.test.ts | 50 ++++++++++++++--------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/resources/common/debug.ts b/src/resources/common/debug.ts index 65e1bf377..774deed61 100644 --- a/src/resources/common/debug.ts +++ b/src/resources/common/debug.ts @@ -62,7 +62,7 @@ export class DebugResource extends ReactiveResource< switch (this.current.tag) { case "connected": { const searchIndexesSupported = await this.session.isSearchIndexSupported(); - result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes." : ""}.`; + result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes" : ""}.`; break; } case "errored": diff --git a/tests/unit/resources/common/debug.test.ts b/tests/unit/resources/common/debug.test.ts index f031fd218..630b7e245 100644 --- a/tests/unit/resources/common/debug.test.ts +++ b/tests/unit/resources/common/debug.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { DebugResource } from "../../../../src/resources/common/debug.js"; import { Session } from "../../../../src/common/session.js"; import { Telemetry } from "../../../../src/telemetry/telemetry.js"; @@ -13,13 +13,15 @@ import { Keychain } from "../../../../src/common/keychain.js"; describe("debug resource", () => { const logger = new CompositeLogger(); const deviceId = DeviceId.create(logger); - const session = new Session({ - apiBaseUrl: "", - logger, - exportsManager: ExportsManager.init(config, logger), - connectionManager: new MCPConnectionManager(config, driverOptions, logger, deviceId), - keychain: new Keychain(), - }); + const session = vi.mocked( + new Session({ + apiBaseUrl: "", + logger, + exportsManager: ExportsManager.init(config, logger), + connectionManager: new MCPConnectionManager(config, driverOptions, logger, deviceId), + keychain: new Keychain(), + }) + ); const telemetry = Telemetry.create(session, { ...config, telemetry: "disabled" }, deviceId); let debugResource: DebugResource = new DebugResource(session, config, telemetry); @@ -28,54 +30,54 @@ describe("debug resource", () => { debugResource = new DebugResource(session, config, telemetry); }); - it("should be connected when a connected event happens", () => { + it("should be connected when a connected event happens", async () => { debugResource.reduceApply("connect", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is connected to the MongoDB cluster.`); }); - it("should be disconnected when a disconnect event happens", () => { + it("should be disconnected when a disconnect event happens", async () => { debugResource.reduceApply("disconnect", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster.`); }); - it("should be disconnected when a close event happens", () => { + it("should be disconnected when a close event happens", async () => { debugResource.reduceApply("close", undefined); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster.`); }); - it("should be disconnected and contain an error when an error event occurred", () => { + it("should be disconnected and contain an error when an error event occurred", async () => { debugResource.reduceApply("connection-error", { tag: "errored", errorReason: "Error message from the server", }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain(`Error message from the server`); }); - it("should show the inferred authentication type", () => { + it("should show the inferred authentication type", async () => { debugResource.reduceApply("connection-error", { tag: "errored", connectionStringAuthType: "scram", errorReason: "Error message from the server", }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain(`The inferred authentication mechanism is "scram".`); expect(output).toContain(`Error message from the server`); }); - it("should show the atlas cluster information when provided", () => { + it("should show the atlas cluster information when provided", async () => { debugResource.reduceApply("connection-error", { tag: "errored", connectionStringAuthType: "scram", @@ -88,7 +90,7 @@ describe("debug resource", () => { }, }); - const output = debugResource.toOutput(); + const output = await debugResource.toOutput(); expect(output).toContain(`The user is not connected to a MongoDB cluster because of an error.`); expect(output).toContain( @@ -97,4 +99,12 @@ describe("debug resource", () => { expect(output).toContain(`The inferred authentication mechanism is "scram".`); expect(output).toContain(`Error message from the server`); }); + + it("should notify if a cluster supports search indexes", async () => { + session.isSearchIndexSupported = vi.fn().mockResolvedValue(true); + debugResource.reduceApply("connect", undefined); + const output = await debugResource.toOutput(); + + expect(output).toContain(`The user is connected to the MongoDB cluster with support for search indexes.`); + }); }); From d7d2b09815eab266d6dd236047e905d94a4c8646 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Thu, 9 Oct 2025 12:47:32 +0200 Subject: [PATCH 5/7] Update src/common/session.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/common/session.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/session.ts b/src/common/session.ts index 9fadf6170..4ec536f4e 100644 --- a/src/common/session.ts +++ b/src/common/session.ts @@ -156,8 +156,8 @@ export class Session extends EventEmitter { async isSearchIndexSupported(): Promise { try { - const dummyDatabase = `db-${Date.now()}`; - const dummyCollection = `coll-${Date.now()}`; + const dummyDatabase = `search-index-test-db-${Date.now()}`; + const dummyCollection = `search-index-test-coll-${Date.now()}`; // If a cluster supports search indexes, the call below will succeed // with a cursor otherwise will throw an Error await this.serviceProvider.getSearchIndexes(dummyDatabase, dummyCollection); From db27dd92ac892391a356235895ca29b2d0c2fdde Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 13 Oct 2025 17:42:58 +0200 Subject: [PATCH 6/7] chore: added clarification for unsupported cases --- src/resources/common/debug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/common/debug.ts b/src/resources/common/debug.ts index 774deed61..f76030b5a 100644 --- a/src/resources/common/debug.ts +++ b/src/resources/common/debug.ts @@ -62,7 +62,7 @@ export class DebugResource extends ReactiveResource< switch (this.current.tag) { case "connected": { const searchIndexesSupported = await this.session.isSearchIndexSupported(); - result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes" : ""}.`; + result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes" : " without any support for search indexes"}.`; break; } case "errored": From 8fac3a2e8d984a371d20b3b9d3a5a890d2006720 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Mon, 13 Oct 2025 18:30:59 +0200 Subject: [PATCH 7/7] chore: fix tests --- tests/unit/resources/common/debug.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/resources/common/debug.test.ts b/tests/unit/resources/common/debug.test.ts index 630b7e245..3a4c68e27 100644 --- a/tests/unit/resources/common/debug.test.ts +++ b/tests/unit/resources/common/debug.test.ts @@ -34,7 +34,9 @@ describe("debug resource", () => { debugResource.reduceApply("connect", undefined); const output = await debugResource.toOutput(); - expect(output).toContain(`The user is connected to the MongoDB cluster.`); + expect(output).toContain( + `The user is connected to the MongoDB cluster without any support for search indexes.` + ); }); it("should be disconnected when a disconnect event happens", async () => {