diff --git a/src/common/config.ts b/src/common/config.ts index e7ece022..85c52085 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -319,7 +319,7 @@ export function warnAboutDeprecatedOrUnknownCliArgs( if (knownArgs.connectionString) { usedDeprecatedArgument = true; warn( - "The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string." + "Warning: The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string." ); } @@ -333,15 +333,15 @@ export function warnAboutDeprecatedOrUnknownCliArgs( if (!valid) { usedInvalidArgument = true; if (suggestion) { - warn(`Invalid command line argument '${providedKey}'. Did you mean '${suggestion}'?`); + warn(`Warning: Invalid command line argument '${providedKey}'. Did you mean '${suggestion}'?`); } else { - warn(`Invalid command line argument '${providedKey}'.`); + warn(`Warning: Invalid command line argument '${providedKey}'.`); } } } if (usedInvalidArgument || usedDeprecatedArgument) { - warn("Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server."); + warn("- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server."); } if (usedInvalidArgument) { @@ -372,6 +372,24 @@ export function registerKnownSecretsInRootKeychain(userConfig: Partial }): UserConfig { const rawConfig = { ...parseEnvConfig(env), @@ -392,6 +410,7 @@ export function setupUserConfig({ cli, env }: { cli: string[]; env: Record !this.isANumber(e))) { + return constructError({ + actualNumDimensions: fieldRef.length, + actualQuantization: "none", + error: "not-numeric", + }); + } + return undefined; case "scalar": case "binary": @@ -251,7 +275,7 @@ export class VectorSearchEmbeddingsManager { }); } - if (!fieldRef.every((e) => this.isANumber(e))) { + if (fieldRef.some((e) => !this.isANumber(e))) { return constructError({ actualNumDimensions: fieldRef.length, actualQuantization: "scalar", diff --git a/src/tools/mongodb/create/createIndex.ts b/src/tools/mongodb/create/createIndex.ts index 68ad4d91..fcbc12ee 100644 --- a/src/tools/mongodb/create/createIndex.ts +++ b/src/tools/mongodb/create/createIndex.ts @@ -80,7 +80,7 @@ export class CreateIndexTool extends MongoDBToolBase { ]) ) .describe( - "The index definition. Use 'classic' for standard indexes and 'vectorSearch' for vector search indexes" + `The index definition. Use 'classic' for standard indexes${this.isFeatureEnabled("vectorSearch") ? " and 'vectorSearch' for vector search indexes" : ""}.` ), }; diff --git a/tests/integration/tools/mongodb/create/createIndex.test.ts b/tests/integration/tools/mongodb/create/createIndex.test.ts index f76bb5ba..a4c4a7be 100644 --- a/tests/integration/tools/mongodb/create/createIndex.test.ts +++ b/tests/integration/tools/mongodb/create/createIndex.test.ts @@ -13,6 +13,22 @@ import { ObjectId, type Collection, type Document, type IndexDirection } from "m import { afterEach, beforeEach, describe, expect, it } from "vitest"; describeWithMongoDB("createIndex tool when search is not enabled", (integration) => { + validateToolMetadata(integration, "create-index", "Create an index for a collection", [ + ...databaseCollectionParameters, + { + name: "definition", + type: "array", + description: "The index definition. Use 'classic' for standard indexes.", + required: true, + }, + { + name: "name", + type: "string", + description: "The name of the index", + required: false, + }, + ]); + it("doesn't allow creating vector search indexes", async () => { expect(integration.mcpServer().userConfig.previewFeatures).to.not.include("vectorSearch"); @@ -99,7 +115,7 @@ describeWithMongoDB( name: "definition", type: "array", description: - "The index definition. Use 'classic' for standard indexes and 'vectorSearch' for vector search indexes", + "The index definition. Use 'classic' for standard indexes and 'vectorSearch' for vector search indexes.", required: true, }, { diff --git a/tests/integration/tools/mongodb/create/insertMany.test.ts b/tests/integration/tools/mongodb/create/insertMany.test.ts index e9964e26..ac72a131 100644 --- a/tests/integration/tools/mongodb/create/insertMany.test.ts +++ b/tests/integration/tools/mongodb/create/insertMany.test.ts @@ -124,6 +124,24 @@ describeWithMongoDB( await collection.drop(); }); + validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [ + ...databaseCollectionParameters, + { + name: "documents", + type: "array", + description: + "The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany().", + required: true, + }, + { + name: "embeddingParameters", + type: "object", + description: + "The embedding model and its parameters to use to generate embeddings for fields with vector search indexes. Note to LLM: If unsure which embedding model to use, ask the user before providing one.", + required: false, + }, + ]); + it("inserts a document when the embedding is correct", async () => { await createVectorSearchIndexAndWait(integration.mongoClient(), database, "test", [ { diff --git a/tests/unit/common/config.test.ts b/tests/unit/common/config.test.ts index 5c671ca7..bc193d85 100644 --- a/tests/unit/common/config.test.ts +++ b/tests/unit/common/config.test.ts @@ -686,14 +686,14 @@ describe("config", () => { describe("CLI arguments", () => { const referDocMessage = - "Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server."; + "- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server."; type TestCase = { readonly cliArg: keyof (CliOptions & UserConfig); readonly warning: string }; const testCases = [ { cliArg: "connectionString", warning: - "The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string.", + "Warning: The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string.", }, ] as TestCase[]; @@ -742,9 +742,9 @@ describe("CLI arguments", () => { { warn, exit } ); - expect(warn).toHaveBeenCalledWith("Invalid command line argument 'wakanda'."); + expect(warn).toHaveBeenCalledWith("Warning: Invalid command line argument 'wakanda'."); expect(warn).toHaveBeenCalledWith( - "Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." + "- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." ); }); @@ -767,9 +767,11 @@ describe("CLI arguments", () => { { warn, exit } ); - expect(warn).toHaveBeenCalledWith("Invalid command line argument 'readonli'. Did you mean 'readOnly'?"); expect(warn).toHaveBeenCalledWith( - "Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." + "Warning: Invalid command line argument 'readonli'. Did you mean 'readOnly'?" + ); + expect(warn).toHaveBeenCalledWith( + "- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." ); }); @@ -781,9 +783,11 @@ describe("CLI arguments", () => { { warn, exit } ); - expect(warn).toHaveBeenCalledWith("Invalid command line argument 'readonly'. Did you mean 'readOnly'?"); expect(warn).toHaveBeenCalledWith( - "Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." + "Warning: Invalid command line argument 'readonly'. Did you mean 'readOnly'?" + ); + expect(warn).toHaveBeenCalledWith( + "- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server." ); }); }); diff --git a/tests/unit/common/search/vectorSearchEmbeddingsManager.test.ts b/tests/unit/common/search/vectorSearchEmbeddingsManager.test.ts index 2bf05146..e1283eed 100644 --- a/tests/unit/common/search/vectorSearchEmbeddingsManager.test.ts +++ b/tests/unit/common/search/vectorSearchEmbeddingsManager.test.ts @@ -42,6 +42,13 @@ const embeddingConfig: Map = n [ mapKey, [ + { + type: "vector", + path: "embedding_field_wo_quantization", + numDimensions: 8, + quantization: "none", + similarity: "euclidean", + }, { type: "vector", path: "embedding_field", @@ -278,51 +285,74 @@ describe("VectorSearchEmbeddingsManager", () => { expect(result).toHaveLength(0); }); - it("documents inserting the field with wrong type are invalid", async () => { - const result = await embeddings.findFieldsWithWrongEmbeddings( - { database, collection }, - { embedding_field: "some text" } - ); - - expect(result).toHaveLength(1); - }); + it.each(["embedding_field", "embedding_field_wo_quantization"] as const)( + "documents inserting the field with wrong type are invalid - $0", + async (field) => { + const result = await embeddings.findFieldsWithWrongEmbeddings( + { database, collection }, + { [field]: "some text" } + ); - it("documents inserting the field with wrong dimensions are invalid", async () => { - const result = await embeddings.findFieldsWithWrongEmbeddings( - { database, collection }, - { embedding_field: [1, 2, 3] } - ); - - expect(result).toHaveLength(1); - const expectedError: VectorFieldValidationError = { - actualNumDimensions: 3, - actualQuantization: "scalar", - error: "dimension-mismatch", - expectedNumDimensions: 8, - expectedQuantization: "scalar", - path: "embedding_field", - }; - expect(result[0]).toEqual(expectedError); - }); + expect(result).toHaveLength(1); + } + ); - it("documents inserting the field with correct dimensions, but wrong type are invalid", async () => { - const result = await embeddings.findFieldsWithWrongEmbeddings( - { database, collection }, - { embedding_field: ["1", "2", "3", "4", "5", "6", "7", "8"] } - ); + it.each([ + { path: "embedding_field", expectedQuantization: "scalar", actualQuantization: "scalar" }, + { + path: "embedding_field_wo_quantization", + expectedQuantization: "none", + actualQuantization: "none", + }, + ] as const)( + "documents inserting the field with wrong dimensions are invalid - path = $path", + async ({ path, expectedQuantization, actualQuantization }) => { + const result = await embeddings.findFieldsWithWrongEmbeddings( + { database, collection }, + { [path]: [1, 2, 3] } + ); + + expect(result).toHaveLength(1); + const expectedError: VectorFieldValidationError = { + actualNumDimensions: 3, + actualQuantization, + error: "dimension-mismatch", + expectedNumDimensions: 8, + expectedQuantization, + path, + }; + expect(result[0]).toEqual(expectedError); + } + ); - expect(result).toHaveLength(1); - const expectedError: VectorFieldValidationError = { - actualNumDimensions: 8, - actualQuantization: "scalar", - error: "not-numeric", - expectedNumDimensions: 8, - expectedQuantization: "scalar", - path: "embedding_field", - }; - - expect(result[0]).toEqual(expectedError); - }); + it.each([ + { path: "embedding_field", expectedQuantization: "scalar", actualQuantization: "scalar" }, + { + path: "embedding_field_wo_quantization", + expectedQuantization: "none", + actualQuantization: "none", + }, + ] as const)( + "documents inserting the field with correct dimensions, but wrong type are invalid - $path", + async ({ path, expectedQuantization, actualQuantization }) => { + const result = await embeddings.findFieldsWithWrongEmbeddings( + { database, collection }, + { [path]: ["1", "2", "3", "4", "5", "6", "7", "8"] } + ); + + expect(result).toHaveLength(1); + const expectedError: VectorFieldValidationError = { + actualNumDimensions: 8, + actualQuantization, + error: "not-numeric", + expectedNumDimensions: 8, + expectedQuantization, + path, + }; + + expect(result[0]).toEqual(expectedError); + } + ); it("documents inserting the field with correct dimensions and quantization in binary are valid", async () => { const result = await embeddings.findFieldsWithWrongEmbeddings(