From ccefe629bd70350b31a67c9aefaf1c58c202b4bf Mon Sep 17 00:00:00 2001 From: nirinchev Date: Wed, 15 Oct 2025 17:39:49 +0200 Subject: [PATCH 1/2] chore: merge list-search-indexes into collection-indexes --- .../mongodb/metadata/collectionIndexes.ts | 61 +++- src/tools/mongodb/search/listSearchIndexes.ts | 81 ----- src/tools/mongodb/tools.ts | 2 - tests/accuracy/collectionIndexes.test.ts | 24 ++ tests/accuracy/listSearchIndexes.test.ts | 28 -- .../tools/mongodb/create/createIndex.test.ts | 10 +- .../mongodb/read/collectionIndexes.test.ts | 285 +++++++++++++++++- .../mongodb/search/listSearchIndexes.test.ts | 126 -------- 8 files changed, 356 insertions(+), 261 deletions(-) delete mode 100644 src/tools/mongodb/search/listSearchIndexes.ts delete mode 100644 tests/accuracy/listSearchIndexes.test.ts delete mode 100644 tests/integration/tools/mongodb/search/listSearchIndexes.test.ts diff --git a/src/tools/mongodb/metadata/collectionIndexes.ts b/src/tools/mongodb/metadata/collectionIndexes.ts index 6da2c7886..6c03ac1c6 100644 --- a/src/tools/mongodb/metadata/collectionIndexes.ts +++ b/src/tools/mongodb/metadata/collectionIndexes.ts @@ -1,7 +1,20 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; import type { ToolArgs, OperationType } from "../../tool.js"; -import { formatUntrustedData } from "../../tool.js"; +import { FeatureFlags, formatUntrustedData } from "../../tool.js"; + +type SearchIndexStatus = { + name: string; + type: string; + status: string; + queryable: boolean; + latestDefinition: Document; +}; + +type IndexStatus = { + name: string; + key: Document; +}; export class CollectionIndexesTool extends MongoDBToolBase { public name = "collection-indexes"; @@ -12,16 +25,30 @@ export class CollectionIndexesTool extends MongoDBToolBase { protected async execute({ database, collection }: ToolArgs): Promise { const provider = await this.ensureConnected(); const indexes = await provider.getIndexes(database, collection); + const indexDefinitions: IndexStatus[] = indexes.map((index) => ({ + name: index.name as string, + key: index.key as Document, + })); + + const searchIndexDefinitions: SearchIndexStatus[] = []; + if (this.isFeatureFlagEnabled(FeatureFlags.VectorSearch) && (await this.session.isSearchSupported())) { + const searchIndexes = await provider.getSearchIndexes(database, collection); + searchIndexDefinitions.push(...this.extractSearchIndexDetails(searchIndexes)); + } return { - content: formatUntrustedData( - `Found ${indexes.length} indexes in the collection "${collection}":`, - indexes.length > 0 - ? indexes - .map((index) => `Name: "${index.name}", definition: ${JSON.stringify(index.key)}`) - .join("\n") - : undefined - ), + content: [ + ...formatUntrustedData( + `Found ${indexDefinitions.length} indexes in the collection "${collection}":`, + indexDefinitions.length > 0 ? JSON.stringify(indexDefinitions, null, 2) : undefined + ), + ...(searchIndexDefinitions.length > 0 + ? formatUntrustedData( + `Found ${searchIndexDefinitions.length} search and vector search indexes in the collection "${collection}":`, + JSON.stringify(searchIndexDefinitions, null, 2) + ) + : []), + ], }; } @@ -43,4 +70,20 @@ export class CollectionIndexesTool extends MongoDBToolBase { return super.handleError(error, args); } + + /** + * Atlas Search index status contains a lot of information that is not relevant for the agent at this stage. + * Like for example, the status on each of the dedicated nodes. We only care about the main status, if it's + * queryable and the index name. We are also picking the index definition as it can be used by the agent to + * understand which fields are available for searching. + **/ + protected extractSearchIndexDetails(indexes: Record[]): SearchIndexStatus[] { + return indexes.map((index) => ({ + name: (index["name"] ?? "default") as string, + type: (index["type"] ?? "UNKNOWN") as string, + status: (index["status"] ?? "UNKNOWN") as string, + queryable: (index["queryable"] ?? false) as boolean, + latestDefinition: index["latestDefinition"] as Document, + })); + } } diff --git a/src/tools/mongodb/search/listSearchIndexes.ts b/src/tools/mongodb/search/listSearchIndexes.ts deleted file mode 100644 index 1b520d523..000000000 --- a/src/tools/mongodb/search/listSearchIndexes.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import type { ToolArgs, OperationType } from "../../tool.js"; -import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js"; -import { formatUntrustedData } from "../../tool.js"; -import { EJSON } from "bson"; - -export type SearchIndexStatus = { - name: string; - type: string; - status: string; - queryable: boolean; - latestDefinition: Document; -}; - -export class ListSearchIndexesTool extends MongoDBToolBase { - public name = "list-search-indexes"; - protected description = "Describes the search and vector search indexes for a single collection"; - protected argsShape = DbOperationArgs; - public operationType: OperationType = "metadata"; - - protected async execute({ database, collection }: ToolArgs): Promise { - const provider = await this.ensureConnected(); - const indexes = await provider.getSearchIndexes(database, collection); - const trimmedIndexDefinitions = this.pickRelevantInformation(indexes); - - if (trimmedIndexDefinitions.length > 0) { - return { - content: formatUntrustedData( - `Found ${trimmedIndexDefinitions.length} search and vector search indexes in ${database}.${collection}`, - trimmedIndexDefinitions.map((index) => EJSON.stringify(index)).join("\n") - ), - }; - } else { - return { - content: formatUntrustedData( - "Could not retrieve search indexes", - `There are no search or vector search indexes in ${database}.${collection}` - ), - }; - } - } - - protected verifyAllowed(): boolean { - // Only enable this on tests for now. - return process.env.VITEST === "true"; - } - - /** - * Atlas Search index status contains a lot of information that is not relevant for the agent at this stage. - * Like for example, the status on each of the dedicated nodes. We only care about the main status, if it's - * queryable and the index name. We are also picking the index definition as it can be used by the agent to - * understand which fields are available for searching. - **/ - protected pickRelevantInformation(indexes: Record[]): SearchIndexStatus[] { - return indexes.map((index) => ({ - name: (index["name"] ?? "default") as string, - type: (index["type"] ?? "UNKNOWN") as string, - status: (index["status"] ?? "UNKNOWN") as string, - queryable: (index["queryable"] ?? false) as boolean, - latestDefinition: index["latestDefinition"] as Document, - })); - } - - protected handleError( - error: unknown, - args: ToolArgs - ): Promise | CallToolResult { - if (error instanceof Error && "codeName" in error && error.codeName === "SearchNotEnabled") { - return { - content: [ - { - text: "This MongoDB cluster does not support Search Indexes. Make sure you are using an Atlas Cluster, either remotely in Atlas or using the Atlas Local image, or your cluster supports MongoDB Search.", - type: "text", - isError: true, - }, - ], - }; - } - return super.handleError(error, args); - } -} diff --git a/src/tools/mongodb/tools.ts b/src/tools/mongodb/tools.ts index 6e96b2ba6..c4498c805 100644 --- a/src/tools/mongodb/tools.ts +++ b/src/tools/mongodb/tools.ts @@ -19,7 +19,6 @@ import { ExplainTool } from "./metadata/explain.js"; import { CreateCollectionTool } from "./create/createCollection.js"; import { LogsTool } from "./metadata/logs.js"; import { ExportTool } from "./read/export.js"; -import { ListSearchIndexesTool } from "./search/listSearchIndexes.js"; import { DropIndexTool } from "./delete/dropIndex.js"; export const MongoDbTools = [ @@ -45,5 +44,4 @@ export const MongoDbTools = [ CreateCollectionTool, LogsTool, ExportTool, - ListSearchIndexesTool, ]; diff --git a/tests/accuracy/collectionIndexes.test.ts b/tests/accuracy/collectionIndexes.test.ts index 45ad2b7e0..73d28a70d 100644 --- a/tests/accuracy/collectionIndexes.test.ts +++ b/tests/accuracy/collectionIndexes.test.ts @@ -37,4 +37,28 @@ describeAccuracyTests([ }, ], }, + { + prompt: "how many search indexes do I have in the collection mydb.mycoll?", + expectedToolCalls: [ + { + toolName: "collection-indexes", + parameters: { + database: "mydb", + collection: "mycoll", + }, + }, + ], + }, + { + prompt: "which vector search indexes do I have in mydb.mycoll?", + expectedToolCalls: [ + { + toolName: "collection-indexes", + parameters: { + database: "mydb", + collection: "mycoll", + }, + }, + ], + }, ]); diff --git a/tests/accuracy/listSearchIndexes.test.ts b/tests/accuracy/listSearchIndexes.test.ts deleted file mode 100644 index 6f4a2d1ce..000000000 --- a/tests/accuracy/listSearchIndexes.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describeAccuracyTests } from "./sdk/describeAccuracyTests.js"; - -describeAccuracyTests([ - { - prompt: "how many search indexes do I have in the collection mydb.mycoll?", - expectedToolCalls: [ - { - toolName: "list-search-indexes", - parameters: { - database: "mydb", - collection: "mycoll", - }, - }, - ], - }, - { - prompt: "which vector search indexes do I have in mydb.mycoll?", - expectedToolCalls: [ - { - toolName: "list-search-indexes", - parameters: { - database: "mydb", - collection: "mycoll", - }, - }, - ], - }, -]); diff --git a/tests/integration/tools/mongodb/create/createIndex.test.ts b/tests/integration/tools/mongodb/create/createIndex.test.ts index ae41869ea..7862d6d95 100644 --- a/tests/integration/tools/mongodb/create/createIndex.test.ts +++ b/tests/integration/tools/mongodb/create/createIndex.test.ts @@ -499,12 +499,10 @@ describeWithMongoDB( }); }, { - getUserConfig: () => { - return { - ...defaultTestConfig, - voyageApiKey: "valid_key", - }; - }, + getUserConfig: () => ({ + ...defaultTestConfig, + voyageApiKey: "valid_key", + }), downloadOptions: { search: true, }, diff --git a/tests/integration/tools/mongodb/read/collectionIndexes.test.ts b/tests/integration/tools/mongodb/read/collectionIndexes.test.ts index d4b4ded04..f9e9d71f2 100644 --- a/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +++ b/tests/integration/tools/mongodb/read/collectionIndexes.test.ts @@ -5,9 +5,19 @@ import { validateThrowsForInvalidArguments, getResponseElements, databaseCollectionInvalidArgs, + getDataFromUntrustedContent, + getResponseContent, + defaultTestConfig, + expectDefined, } from "../../../helpers.js"; -import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js"; -import { expect, it } from "vitest"; +import { + describeWithMongoDB, + validateAutoConnectBehavior, + waitUntilSearchIndexIsQueryable, + waitUntilSearchIsReady, +} from "../mongodbHelpers.js"; +import { beforeEach, describe, expect, it } from "vitest"; +import type { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver"; describeWithMongoDB("collectionIndexes tool", (integration) => { validateToolMetadata( @@ -48,7 +58,8 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(2); expect(elements[0]?.text).toEqual('Found 1 indexes in the collection "people":'); - expect(elements[1]?.text).toContain('Name: "_id_", definition: {"_id":1}'); + const indexDefinitions = JSON.parse(getDataFromUntrustedContent(elements[1]?.text || "")) as []; + expect(indexDefinitions).toEqual([{ name: "_id_", key: { _id: 1 } }]); }); it("returns all indexes for a collection", async () => { @@ -79,17 +90,19 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { expect(elements).toHaveLength(2); expect(elements[0]?.text).toEqual(`Found ${indexTypes.length + 1} indexes in the collection "people":`); - expect(elements[1]?.text).toContain('Name: "_id_", definition: {"_id":1}'); + const indexDefinitions = JSON.parse(getDataFromUntrustedContent(elements[1]?.text || "")) as []; + expect(indexDefinitions).toContainEqual({ name: "_id_", key: { _id: 1 } }); for (const indexType of indexTypes) { - let expectedDefinition = JSON.stringify({ [`prop_${indexType}`]: indexType }); + let expectedDefinition = { [`prop_${indexType}`]: indexType }; if (indexType === "text") { - expectedDefinition = '{"_fts":"text"'; + expectedDefinition = { _fts: "text", _ftsx: 1 }; } - expect(elements[1]?.text).toContain( - `Name: "${indexNames.get(indexType)}", definition: ${expectedDefinition}` - ); + expect(indexDefinitions).toContainEqual({ + name: indexNames.get(indexType), + key: expectedDefinition, + }); } }); @@ -100,3 +113,257 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { }; }); }); +const SEARCH_TIMEOUT = 20_000; + +describeWithMongoDB( + "collection-indexes tool with Search", + (integration) => { + let provider: NodeDriverServiceProvider; + + beforeEach(async ({ signal }) => { + await integration.connectMcpClient(); + provider = integration.mcpServer().session.serviceProvider; + await waitUntilSearchIsReady(provider, signal); + }); + + describe("when the collection does not exist", () => { + it("returns an empty list of indexes", async () => { + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: "any", collection: "foo" }, + }); + const responseContent = getResponseContent(response.content); + expect(responseContent).toContain( + 'The indexes for "any.foo" cannot be determined because the collection does not exist.' + ); + }); + }); + + describe("when there are no search indexes", () => { + beforeEach(async () => { + await provider.createIndexes(integration.randomDbName(), "foo", [{ key: { foo: 1 } }]); + }); + + it("returns an just the regular indexes", async () => { + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: integration.randomDbName(), collection: "foo" }, + }); + + const responseElements = getResponseElements(response.content); + expect(responseElements).toHaveLength(2); + // Expect 2 indexes - _id_ and foo_1 + expect(responseElements[0]?.text).toContain('Found 2 indexes in the collection "foo"'); + + const responseContent = getResponseContent(response.content); + expect(responseContent).not.toContain("search and vector search indexes"); + }); + }); + + describe("when there are vector search indexes", () => { + beforeEach(async () => { + await provider.insertOne(integration.randomDbName(), "foo", { + field1: "yay", + age: 1, + field1_embeddings: [1, 2, 3, 4], + }); + await provider.createSearchIndexes(integration.randomDbName(), "foo", [ + { + name: "my-vector-index", + definition: { + fields: [ + { type: "vector", path: "field1_embeddings", numDimensions: 4, similarity: "cosine" }, + ], + }, + type: "vectorSearch", + }, + { + name: "my-mixed-index", + definition: { + fields: [ + { + type: "vector", + path: "field1_embeddings", + numDimensions: 4, + similarity: "euclidean", + }, + { type: "filter", path: "age" }, + ], + }, + type: "vectorSearch", + }, + ]); + }); + + it("returns the list of existing indexes", { timeout: SEARCH_TIMEOUT }, async () => { + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: integration.randomDbName(), collection: "foo" }, + }); + + const elements = getResponseElements(response.content); + expect(elements).toHaveLength(4); + + // Expect 1 regular index - _id_ + expect(elements[0]?.text).toContain(`Found 1 indexes in the collection "foo":`); + expect(elements[2]?.text).toContain( + `Found 2 search and vector search indexes in the collection "foo":` + ); + + const indexDefinitions = JSON.parse(getDataFromUntrustedContent(elements[3]?.text || "")) as { + name: string; + type: string; + latestDefinition: { fields: unknown[] }; + }[]; + expect(indexDefinitions).toHaveLength(2); + expect(indexDefinitions[0]).toHaveProperty("name", "my-vector-index"); + expect(indexDefinitions[0]).toHaveProperty("type", "vectorSearch"); + const fields0 = indexDefinitions[0]?.latestDefinition.fields; + expectDefined(fields0); + expect(fields0).toHaveLength(1); + expect(fields0[0]).toHaveProperty("type", "vector"); + expect(fields0[0]).toHaveProperty("path", "field1_embeddings"); + + expect(indexDefinitions[1]).toHaveProperty("name", "my-mixed-index"); + expect(indexDefinitions[1]).toHaveProperty("type", "vectorSearch"); + const fields1 = indexDefinitions[1]?.latestDefinition.fields; + expectDefined(fields1); + expect(fields1).toHaveLength(2); + expect(fields1[0]).toHaveProperty("type", "vector"); + expect(fields1[0]).toHaveProperty("path", "field1_embeddings"); + expect(fields1[1]).toHaveProperty("type", "filter"); + expect(fields1[1]).toHaveProperty("path", "age"); + }); + + it( + "returns the list of existing indexes and detects if they are queryable", + { timeout: SEARCH_TIMEOUT }, + async ({ signal }) => { + await waitUntilSearchIndexIsQueryable( + provider, + integration.randomDbName(), + "foo", + "my-vector-index", + signal + ); + await waitUntilSearchIndexIsQueryable( + provider, + integration.randomDbName(), + "foo", + "my-mixed-index", + signal + ); + + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: integration.randomDbName(), collection: "foo" }, + }); + + const elements = getResponseElements(response.content); + const indexDefinitions = JSON.parse(getDataFromUntrustedContent(elements[3]?.text || "")) as { + name: string; + }[]; + + const vectorIndexDefinition = indexDefinitions.find((def) => def.name === "my-vector-index"); + + expect(vectorIndexDefinition).toHaveProperty("queryable", true); + expect(vectorIndexDefinition).toHaveProperty("status", "READY"); + + const mixedIndexDefinition = indexDefinitions.find((def) => def.name === "my-mixed-index"); + expect(mixedIndexDefinition).toHaveProperty("queryable", true); + expect(mixedIndexDefinition).toHaveProperty("status", "READY"); + } + ); + }); + + describe("when there are Atlas search indexes", () => { + beforeEach(async () => { + await provider.insertOne(integration.randomDbName(), "foo", { field1: "yay", age: 1 }); + await provider.createSearchIndexes(integration.randomDbName(), "foo", [ + { name: "my-search-index", definition: { mappings: { dynamic: true } }, type: "search" }, + ]); + }); + + it("returns them alongside the regular indexes", async () => { + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: integration.randomDbName(), collection: "foo" }, + }); + + const elements = getResponseElements(response.content); + expect(elements).toHaveLength(4); + // Expect 1 regular index - _id_ + expect(elements[0]?.text).toContain(`Found 1 indexes in the collection "foo":`); + expect(elements[2]?.text).toContain( + `Found 1 search and vector search indexes in the collection "foo":` + ); + + const indexDefinitions = JSON.parse(getDataFromUntrustedContent(elements[3]?.text || "")) as { + name: string; + type: string; + latestDefinition: unknown; + }[]; + + expect(indexDefinitions).toHaveLength(1); + expect(indexDefinitions[0]).toHaveProperty("name", "my-search-index"); + expect(indexDefinitions[0]).toHaveProperty("type", "search"); + expect(indexDefinitions[0]).toHaveProperty("latestDefinition", { + mappings: { dynamic: true, fields: {} }, + }); + }); + }); + }, + { + getUserConfig: () => ({ + ...defaultTestConfig, + voyageApiKey: "valid_key", + }), + downloadOptions: { search: true }, + } +); + +describeWithMongoDB( + "collectionIndexes tool without voyage API key", + (integration) => { + let provider: NodeDriverServiceProvider; + + beforeEach(async ({ signal }) => { + await integration.connectMcpClient(); + provider = integration.mcpServer().session.serviceProvider; + await waitUntilSearchIsReady(provider, signal); + + await provider.insertOne(integration.randomDbName(), "foo", { field1: "yay", age: 1 }); + await provider.createSearchIndexes(integration.randomDbName(), "foo", [ + { + name: "my-vector-index", + definition: { + fields: [{ type: "vector", path: "field1_embeddings", numDimensions: 4, similarity: "cosine" }], + }, + type: "vectorSearch", + }, + ]); + }); + it("does not return search indexes", async () => { + const response = await integration.mcpClient().callTool({ + name: "collection-indexes", + arguments: { database: integration.randomDbName(), collection: "foo" }, + }); + + const elements = getResponseElements(response.content); + expect(elements).toHaveLength(2); + // Expect 1 regular index - _id_ + expect(elements[0]?.text).toContain(`Found 1 indexes in the collection "foo"`); + + const responseContent = getResponseContent(response.content); + expect(responseContent).not.toContain("search and vector search indexes"); + + // Ensure that we do have search indexes + const searchIndexes = await provider.getSearchIndexes(integration.randomDbName(), "foo"); + expect(searchIndexes).toHaveLength(1); + expect(searchIndexes[0]).toHaveProperty("name", "my-vector-index"); + }); + }, + { + downloadOptions: { search: true }, + } +); diff --git a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts b/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts deleted file mode 100644 index 477f9faee..000000000 --- a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - describeWithMongoDB, - getSingleDocFromUntrustedContent, - waitUntilSearchIndexIsQueryable, - waitUntilSearchIsReady, -} from "../mongodbHelpers.js"; -import { describe, it, expect, beforeEach } from "vitest"; -import { - getResponseContent, - databaseCollectionParameters, - validateToolMetadata, - validateThrowsForInvalidArguments, - databaseCollectionInvalidArgs, - getDataFromUntrustedContent, -} from "../../../helpers.js"; -import type { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver"; -import type { SearchIndexStatus } from "../../../../../src/tools/mongodb/search/listSearchIndexes.js"; - -const SEARCH_TIMEOUT = 20_000; - -describeWithMongoDB("list search indexes tool in local MongoDB", (integration) => { - validateToolMetadata( - integration, - "list-search-indexes", - "Describes the search and vector search indexes for a single collection", - databaseCollectionParameters - ); - - validateThrowsForInvalidArguments(integration, "list-search-indexes", databaseCollectionInvalidArgs); - - it("fails for clusters without MongoDB Search", async () => { - await integration.connectMcpClient(); - const response = await integration.mcpClient().callTool({ - name: "list-search-indexes", - arguments: { database: "any", collection: "foo" }, - }); - const content = getResponseContent(response.content); - expect(content).toEqual( - "This MongoDB cluster does not support Search Indexes. Make sure you are using an Atlas Cluster, either remotely in Atlas or using the Atlas Local image, or your cluster supports MongoDB Search." - ); - }); -}); - -describeWithMongoDB( - "list search indexes tool in Atlas", - (integration) => { - let provider: NodeDriverServiceProvider; - - beforeEach(async ({ signal }) => { - await integration.connectMcpClient(); - provider = integration.mcpServer().session.serviceProvider; - await waitUntilSearchIsReady(provider, signal); - }); - - describe("when the collection does not exist", () => { - it("returns an empty list of indexes", async () => { - const response = await integration.mcpClient().callTool({ - name: "list-search-indexes", - arguments: { database: "any", collection: "foo" }, - }); - const responseContent = getResponseContent(response.content); - const content = getDataFromUntrustedContent(responseContent); - expect(responseContent).toContain("Could not retrieve search indexes"); - expect(content).toEqual("There are no search or vector search indexes in any.foo"); - }); - }); - - describe("when there are no indexes", () => { - it("returns an empty list of indexes", async () => { - const response = await integration.mcpClient().callTool({ - name: "list-search-indexes", - arguments: { database: "any", collection: "foo" }, - }); - const responseContent = getResponseContent(response.content); - const content = getDataFromUntrustedContent(responseContent); - expect(responseContent).toContain("Could not retrieve search indexes"); - expect(content).toEqual("There are no search or vector search indexes in any.foo"); - }); - }); - - describe("when there are indexes", () => { - beforeEach(async () => { - await provider.insertOne("any", "foo", { field1: "yay" }); - await provider.createSearchIndexes("any", "foo", [{ definition: { mappings: { dynamic: true } } }]); - }); - - it("returns the list of existing indexes", { timeout: SEARCH_TIMEOUT }, async () => { - const response = await integration.mcpClient().callTool({ - name: "list-search-indexes", - arguments: { database: "any", collection: "foo" }, - }); - const content = getResponseContent(response.content); - const indexDefinition = getSingleDocFromUntrustedContent(content); - - expect(indexDefinition?.name).toEqual("default"); - expect(indexDefinition?.type).toEqual("search"); - expect(indexDefinition?.latestDefinition).toEqual({ mappings: { dynamic: true, fields: {} } }); - }); - - it( - "returns the list of existing indexes and detects if they are queryable", - { timeout: SEARCH_TIMEOUT }, - async ({ signal }) => { - await waitUntilSearchIndexIsQueryable(provider, "any", "foo", "default", signal); - - const response = await integration.mcpClient().callTool({ - name: "list-search-indexes", - arguments: { database: "any", collection: "foo" }, - }); - - const content = getResponseContent(response.content); - const indexDefinition = getSingleDocFromUntrustedContent(content); - - expect(indexDefinition?.name).toEqual("default"); - expect(indexDefinition?.type).toEqual("search"); - expect(indexDefinition?.latestDefinition).toEqual({ mappings: { dynamic: true, fields: {} } }); - expect(indexDefinition?.queryable).toEqual(true); - expect(indexDefinition?.status).toEqual("READY"); - } - ); - }); - }, - { - downloadOptions: { search: true }, - } -); From 56a50b7b27c4a5097ffea0e5487550a0333de967 Mon Sep 17 00:00:00 2001 From: nirinchev Date: Mon, 20 Oct 2025 10:03:59 +0300 Subject: [PATCH 2/2] remove an unnecessary an --- .../tools/mongodb/metadata/collectionIndexes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/tools/mongodb/metadata/collectionIndexes.test.ts b/tests/integration/tools/mongodb/metadata/collectionIndexes.test.ts index b6e795b65..868d8d0a1 100644 --- a/tests/integration/tools/mongodb/metadata/collectionIndexes.test.ts +++ b/tests/integration/tools/mongodb/metadata/collectionIndexes.test.ts @@ -149,7 +149,7 @@ describeWithMongoDB( await collection.createIndexes([{ key: { foo: 1 } }]); }); - it("returns an just the regular indexes", async () => { + it("returns just the regular indexes", async () => { const response = await integration.mcpClient().callTool({ name: "collection-indexes", arguments: { database: integration.randomDbName(), collection: "foo" },