Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 74 additions & 8 deletions src/tools/mongodb/create/createIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,91 @@ export class CreateIndexTool extends MongoDBToolBase {
protected description = "Create an index for a collection";
protected argsShape = {
...DbOperationArgs,
keys: z.object({}).catchall(z.custom<IndexDirection>()).describe("The index definition"),
name: z.string().optional().describe("The name of the index"),
definition: z
.discriminatedUnion("type", [
z.object({
type: z.literal("classic"),
keys: z.object({}).catchall(z.custom<IndexDirection>()).describe("The index definition"),
}),
z.object({
type: z.literal("vectorSearch"),
fields: z
.array(
z.object({
type: z
.enum(["vector", "filter"])
.describe(
"Field type to use to index fields for vector search. You must specify `vector` for fields that contain vector embeddings and `filter` for additional fields to filter on."
),
path: z
.string()
.describe(
"Name of the field to index. For nested fields, use dot notation to specify path to embedded fields"
),
numDimensions: z
.number()
.min(1)
.max(8192)
.describe(
"Number of vector dimensions that MongoDB Vector Search enforces at index-time and query-time"
),
similarity: z
.enum(["cosine", "euclidean", "dotProduct"])
.describe(
"Vector similarity function to use to search for top K-nearest neighbors. You can set this field only for vector-type fields."
),
quantization: z
.enum(["none", "scalar", "binary"])
.optional()
.default("none")
.describe(
"Type of automatic vector quantization for your vectors. Use this setting only if your embeddings are float or double vectors."
),
})
)
.describe(
"Definitions for the vector and filter fields to index, one definition per document. The fields array must contain at least one vector-type field definition."
),
}),
])
.describe(
"The index definition. Use 'classic' for standard indexes and 'vectorSearch' for vector search indexes"
),
};

public operationType: OperationType = "create";

protected async execute({
database,
collection,
keys,
name,
definition,
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
const provider = await this.ensureConnected();
const indexes = await provider.createIndexes(database, collection, [
{
key: keys,
name,
},
]);
let indexes: string[] = [];
switch (definition.type) {
case "classic":
indexes = await provider.createIndexes(database, collection, [
{
key: definition.keys,
name,
},
]);

break;
case "vectorSearch":
indexes = await provider.createSearchIndexes(database, collection, [
{
name,
definition: {
fields: definition.fields,
},
type: "vectorSearch",
},
]);
break;
}

return {
content: [
Expand Down
14 changes: 10 additions & 4 deletions tests/accuracy/createIndex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ describeAccuracyTests([
database: "mflix",
collection: "movies",
name: Matcher.anyOf(Matcher.undefined, Matcher.string()),
keys: {
release_year: 1,
definition: {
type: "classic",
keys: {
release_year: 1,
},
},
},
},
Expand All @@ -27,8 +30,11 @@ describeAccuracyTests([
database: "mflix",
collection: "movies",
name: Matcher.anyOf(Matcher.undefined, Matcher.string()),
keys: {
title: "text",
definition: {
type: "classic",
keys: {
title: "text",
},
},
},
},
Expand Down
51 changes: 40 additions & 11 deletions tests/integration/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@ export const driverOptions = setupDriverConfig({

export const defaultDriverOptions: DriverOptions = { ...driverOptions };

interface ParameterInfo {
interface Parameter {
name: string;
type: string;
description: string;
required: boolean;
}

interface SingleValueParameter extends Parameter {
type: string;
}

interface AnyOfParameter extends Parameter {
anyOf: { type: string }[];
}

type ParameterInfo = SingleValueParameter | AnyOfParameter;

type ToolInfo = Awaited<ReturnType<Client["listTools"]>>["tools"][number];

export interface IntegrationTest {
Expand Down Expand Up @@ -217,18 +226,38 @@ export function getParameters(tool: ToolInfo): ParameterInfo[] {

return Object.entries(tool.inputSchema.properties)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([key, value]) => {
expect(value).toHaveProperty("type");
.map(([name, value]) => {
expect(value).toHaveProperty("description");

const typedValue = value as { type: string; description: string };
expect(typeof typedValue.type).toBe("string");
expect(typeof typedValue.description).toBe("string");
const description = (value as { description: string }).description;
const required = (tool.inputSchema.required as string[])?.includes(name) ?? false;
expect(typeof description).toBe("string");

if (value && typeof value === "object" && "anyOf" in value) {
const typedOptions = new Array<{ type: string }>();
for (const option of value.anyOf as { type: string }[]) {
expect(option).toHaveProperty("type");

typedOptions.push({ type: option.type });
}

return {
name,
anyOf: typedOptions,
description: description,
required,
};
}

expect(value).toHaveProperty("type");

const type = (value as { type: string }).type;
expect(typeof type).toBe("string");
return {
name: key,
type: typedValue.type,
description: typedValue.description,
required: (tool.inputSchema.required as string[])?.includes(key) ?? false,
name,
type,
description,
required,
};
});
}
Expand Down
Loading
Loading