Skip to content

Commit 88f5640

Browse files
committed
Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/typescript-eslint-8.46.3
2 parents d0d4f7d + 9e325f9 commit 88f5640

File tree

20 files changed

+523
-218
lines changed

20 files changed

+523
-218
lines changed

.github/workflows/code-health-fork.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,18 @@ jobs:
3636
run: npm ci
3737
- name: Run tests
3838
run: npm test
39-
env:
40-
SKIP_ATLAS_TESTS: "true"
41-
SKIP_ATLAS_LOCAL_TESTS: "true"
42-
- name: Upload test results
43-
if: always() && matrix.os == 'ubuntu-latest'
44-
uses: actions/upload-artifact@v5
39+
run-atlas-local-tests:
40+
name: Run Atlas Local tests
41+
if: github.event.pull_request.user.login == 'dependabot[bot]'
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
45+
- uses: actions/checkout@v5
46+
- uses: actions/setup-node@v4
4547
with:
46-
name: test-results
47-
path: coverage/lcov.info
48+
node-version-file: package.json
49+
cache: "npm"
50+
- name: Install dependencies
51+
run: npm ci
52+
- name: Run tests
53+
run: npm test -- tests/integration/tools/atlas-local/

package-lock.json

Lines changed: 16 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/config.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export function warnAboutDeprecatedOrUnknownCliArgs(
319319
if (knownArgs.connectionString) {
320320
usedDeprecatedArgument = true;
321321
warn(
322-
"The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string."
322+
"Warning: The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string."
323323
);
324324
}
325325

@@ -333,15 +333,15 @@ export function warnAboutDeprecatedOrUnknownCliArgs(
333333
if (!valid) {
334334
usedInvalidArgument = true;
335335
if (suggestion) {
336-
warn(`Invalid command line argument '${providedKey}'. Did you mean '${suggestion}'?`);
336+
warn(`Warning: Invalid command line argument '${providedKey}'. Did you mean '${suggestion}'?`);
337337
} else {
338-
warn(`Invalid command line argument '${providedKey}'.`);
338+
warn(`Warning: Invalid command line argument '${providedKey}'.`);
339339
}
340340
}
341341
}
342342

343343
if (usedInvalidArgument || usedDeprecatedArgument) {
344-
warn("Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server.");
344+
warn("- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server.");
345345
}
346346

347347
if (usedInvalidArgument) {
@@ -372,6 +372,24 @@ export function registerKnownSecretsInRootKeychain(userConfig: Partial<UserConfi
372372
maybeRegister(userConfig.username, "user");
373373
}
374374

375+
export function warnIfVectorSearchNotEnabledCorrectly(config: UserConfig, warn: (message: string) => void): void {
376+
const vectorSearchEnabled = config.previewFeatures.includes("vectorSearch");
377+
const embeddingsProviderConfigured = !!config.voyageApiKey;
378+
if (vectorSearchEnabled && !embeddingsProviderConfigured) {
379+
warn(`\
380+
Warning: Vector search is enabled but no embeddings provider is configured.
381+
- Set an embeddings provider configuration option to enable auto-embeddings during document insertion and text-based queries with $vectorSearch.\
382+
`);
383+
}
384+
385+
if (!vectorSearchEnabled && embeddingsProviderConfigured) {
386+
warn(`\
387+
Warning: An embeddings provider is configured but the 'vectorSearch' preview feature is not enabled.
388+
- Enable vector search by adding 'vectorSearch' to the 'previewFeatures' configuration option, or remove the embeddings provider configuration if not needed.\
389+
`);
390+
}
391+
}
392+
375393
export function setupUserConfig({ cli, env }: { cli: string[]; env: Record<string, unknown> }): UserConfig {
376394
const rawConfig = {
377395
...parseEnvConfig(env),
@@ -392,6 +410,7 @@ export function setupUserConfig({ cli, env }: { cli: string[]; env: Record<strin
392410
// We don't have as schema defined for all args-parser arguments so we need to merge the raw config with the parsed config.
393411
const userConfig = { ...rawConfig, ...parseResult.data } as UserConfig;
394412

413+
warnIfVectorSearchNotEnabledCorrectly(userConfig, (message) => console.warn(message));
395414
registerKnownSecretsInRootKeychain(userConfig);
396415
return userConfig;
397416
}

src/common/search/vectorSearchEmbeddingsManager.ts

Lines changed: 45 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ export type VectorFieldIndexDefinition = {
2424
export type VectorFieldValidationError = {
2525
path: string;
2626
expectedNumDimensions: number;
27-
expectedQuantization: Quantization;
2827
actualNumDimensions: number | "unknown";
29-
actualQuantization: Quantization | "unknown";
30-
error: "dimension-mismatch" | "quantization-mismatch" | "not-a-vector" | "not-numeric";
28+
error: "dimension-mismatch" | "not-a-vector" | "not-numeric";
3129
};
3230

3331
export type EmbeddingNamespace = `${string}.${string}`;
@@ -116,9 +114,9 @@ export class VectorSearchEmbeddingsManager {
116114
if (embeddingValidationResults.length > 0) {
117115
const embeddingValidationMessages = embeddingValidationResults.map(
118116
(validation) =>
119-
`- Field ${validation.path} is an embedding with ${validation.expectedNumDimensions} dimensions and ${validation.expectedQuantization}` +
120-
` quantization, and the provided value is not compatible. Actual dimensions: ${validation.actualNumDimensions}, ` +
121-
`actual quantization: ${validation.actualQuantization}. Error: ${validation.error}`
117+
`- Field ${validation.path} is an embedding with ${validation.expectedNumDimensions} dimensions,` +
118+
` and the provided value is not compatible. Actual dimensions: ${validation.actualNumDimensions},` +
119+
` Error: ${validation.error}`
122120
);
123121

124122
throw new MongoDBError(
@@ -179,16 +177,36 @@ export class VectorSearchEmbeddingsManager {
179177
let fieldRef: unknown = document;
180178

181179
const constructError = (
182-
details: Partial<Pick<VectorFieldValidationError, "error" | "actualNumDimensions" | "actualQuantization">>
180+
details: Partial<Pick<VectorFieldValidationError, "error" | "actualNumDimensions">>
183181
): VectorFieldValidationError => ({
184182
path: definition.path,
185183
expectedNumDimensions: definition.numDimensions,
186-
expectedQuantization: definition.quantization,
187184
actualNumDimensions: details.actualNumDimensions ?? "unknown",
188-
actualQuantization: details.actualQuantization ?? "unknown",
189185
error: details.error ?? "not-a-vector",
190186
});
191187

188+
const extractUnderlyingVector = (fieldRef: unknown): ArrayLike<unknown> | undefined => {
189+
if (fieldRef instanceof BSON.Binary) {
190+
try {
191+
return fieldRef.toFloat32Array();
192+
} catch {
193+
// nothing to do here
194+
}
195+
196+
try {
197+
return fieldRef.toBits();
198+
} catch {
199+
// nothing to do here
200+
}
201+
}
202+
203+
if (Array.isArray(fieldRef)) {
204+
return fieldRef as Array<unknown>;
205+
}
206+
207+
return undefined;
208+
};
209+
192210
for (const field of fieldPath) {
193211
if (fieldRef && typeof fieldRef === "object" && field in fieldRef) {
194212
fieldRef = (fieldRef as Record<string, unknown>)[field];
@@ -197,70 +215,25 @@ export class VectorSearchEmbeddingsManager {
197215
}
198216
}
199217

200-
switch (definition.quantization) {
201-
// Because quantization is not defined by the user
202-
// we have to trust them in the format they use.
203-
case "none":
204-
return undefined;
205-
case "scalar":
206-
case "binary":
207-
if (fieldRef instanceof BSON.Binary) {
208-
try {
209-
const elements = fieldRef.toFloat32Array();
210-
if (elements.length !== definition.numDimensions) {
211-
return constructError({
212-
actualNumDimensions: elements.length,
213-
actualQuantization: "binary",
214-
error: "dimension-mismatch",
215-
});
216-
}
217-
218-
return undefined;
219-
} catch {
220-
// bits are also supported
221-
try {
222-
const bits = fieldRef.toBits();
223-
if (bits.length !== definition.numDimensions) {
224-
return constructError({
225-
actualNumDimensions: bits.length,
226-
actualQuantization: "binary",
227-
error: "dimension-mismatch",
228-
});
229-
}
230-
231-
return undefined;
232-
} catch {
233-
return constructError({
234-
actualQuantization: "binary",
235-
error: "not-a-vector",
236-
});
237-
}
238-
}
239-
} else {
240-
if (!Array.isArray(fieldRef)) {
241-
return constructError({
242-
error: "not-a-vector",
243-
});
244-
}
245-
246-
if (fieldRef.length !== definition.numDimensions) {
247-
return constructError({
248-
actualNumDimensions: fieldRef.length,
249-
actualQuantization: "scalar",
250-
error: "dimension-mismatch",
251-
});
252-
}
253-
254-
if (!fieldRef.every((e) => this.isANumber(e))) {
255-
return constructError({
256-
actualNumDimensions: fieldRef.length,
257-
actualQuantization: "scalar",
258-
error: "not-numeric",
259-
});
260-
}
261-
}
218+
const maybeVector = extractUnderlyingVector(fieldRef);
219+
if (!maybeVector) {
220+
return constructError({
221+
error: "not-a-vector",
222+
});
223+
}
262224

263-
break;
225+
if (maybeVector.length !== definition.numDimensions) {
226+
return constructError({
227+
actualNumDimensions: maybeVector.length,
228+
error: "dimension-mismatch",
229+
});
230+
}
231+
232+
if (Array.isArray(maybeVector) && maybeVector.some((e) => !this.isANumber(e))) {
233+
return constructError({
234+
actualNumDimensions: maybeVector.length,
235+
error: "not-numeric",
236+
});
264237
}
265238

266239
return undefined;

src/tools/atlas/connect/connectCluster.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,39 @@ export class ConnectClusterTool extends AtlasToolBase {
218218
const ipAccessListUpdated = await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
219219
let createdUser = false;
220220

221+
const state = this.queryConnection(projectId, clusterName);
222+
switch (state) {
223+
case "connected-to-other-cluster":
224+
case "disconnected": {
225+
await this.session.disconnect();
226+
227+
const { connectionString, atlas } = await this.prepareClusterConnection(
228+
projectId,
229+
clusterName,
230+
connectionType
231+
);
232+
233+
createdUser = true;
234+
235+
// try to connect for about 5 minutes asynchronously
236+
void this.connectToCluster(connectionString, atlas).catch((err: unknown) => {
237+
const error = err instanceof Error ? err : new Error(String(err));
238+
this.session.logger.error({
239+
id: LogId.atlasConnectFailure,
240+
context: "atlas-connect-cluster",
241+
message: `error connecting to cluster: ${error.message}`,
242+
});
243+
});
244+
break;
245+
}
246+
case "connecting":
247+
case "connected":
248+
case "unknown":
249+
default: {
250+
break;
251+
}
252+
}
253+
221254
for (let i = 0; i < 60; i++) {
222255
const state = this.queryConnection(projectId, clusterName);
223256
switch (state) {
@@ -246,34 +279,15 @@ export class ConnectClusterTool extends AtlasToolBase {
246279
return { content };
247280
}
248281
case "connecting":
249-
case "unknown": {
250-
break;
251-
}
282+
case "unknown":
252283
case "connected-to-other-cluster":
253284
case "disconnected":
254285
default: {
255-
await this.session.disconnect();
256-
const { connectionString, atlas } = await this.prepareClusterConnection(
257-
projectId,
258-
clusterName,
259-
connectionType
260-
);
261-
262-
createdUser = true;
263-
// try to connect for about 5 minutes asynchronously
264-
void this.connectToCluster(connectionString, atlas).catch((err: unknown) => {
265-
const error = err instanceof Error ? err : new Error(String(err));
266-
this.session.logger.error({
267-
id: LogId.atlasConnectFailure,
268-
context: "atlas-connect-cluster",
269-
message: `error connecting to cluster: ${error.message}`,
270-
});
271-
});
272286
break;
273287
}
274288
}
275289

276-
await sleep(500);
290+
await sleep(500); // wait 500ms before checking the connection state again
277291
}
278292

279293
const content: CallToolResult["content"] = [

src/tools/mongodb/create/createIndex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class CreateIndexTool extends MongoDBToolBase {
8080
])
8181
)
8282
.describe(
83-
"The index definition. Use 'classic' for standard indexes and 'vectorSearch' for vector search indexes"
83+
`The index definition. Use 'classic' for standard indexes${this.isFeatureEnabled("vectorSearch") ? " and 'vectorSearch' for vector search indexes" : ""}.`
8484
),
8585
};
8686

src/tools/mongodb/metadata/explain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ToolArgs, OperationType } from "../../tool.js";
44
import { formatUntrustedData } from "../../tool.js";
55
import { z } from "zod";
66
import type { Document } from "mongodb";
7-
import { AggregateArgs } from "../read/aggregate.js";
7+
import { getAggregateArgs } from "../read/aggregate.js";
88
import { FindArgs } from "../read/find.js";
99
import { CountArgs } from "../read/count.js";
1010

@@ -20,7 +20,7 @@ export class ExplainTool extends MongoDBToolBase {
2020
z.discriminatedUnion("name", [
2121
z.object({
2222
name: z.literal("aggregate"),
23-
arguments: z.object(AggregateArgs),
23+
arguments: z.object(getAggregateArgs(this.isFeatureEnabled("vectorSearch"))),
2424
}),
2525
z.object({
2626
name: z.literal("find"),

src/tools/mongodb/mongodbSchemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export type EmbeddingParameters = {
4242
export const zSupportedEmbeddingParameters = zVoyageEmbeddingParameters.extend({ model: zVoyageModels });
4343
export type SupportedEmbeddingParameters = z.infer<typeof zSupportedEmbeddingParameters>;
4444

45-
export const AnyVectorSearchStage = zEJSON();
45+
export const AnyAggregateStage = zEJSON();
4646
export const VectorSearchStage = z.object({
4747
$vectorSearch: z
4848
.object({

0 commit comments

Comments
 (0)