Skip to content

Commit b842fa4

Browse files
authored
chore: add integration tests for create-index (#84)
1 parent 58dc5bd commit b842fa4

File tree

9 files changed

+399
-52
lines changed

9 files changed

+399
-52
lines changed

src/tools/mongodb/create/createIndex.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,29 @@ export class CreateIndexTool extends MongoDBToolBase {
1010
protected argsShape = {
1111
...DbOperationArgs,
1212
keys: z.record(z.string(), z.custom<IndexDirection>()).describe("The index definition"),
13+
name: z.string().optional().describe("The name of the index"),
1314
};
1415

1516
protected operationType: OperationType = "create";
1617

17-
protected async execute({ database, collection, keys }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
18+
protected async execute({
19+
database,
20+
collection,
21+
keys,
22+
name,
23+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
1824
const provider = await this.ensureConnected();
1925
const indexes = await provider.createIndexes(database, collection, [
2026
{
2127
key: keys,
28+
name,
2229
},
2330
]);
2431

2532
return {
2633
content: [
2734
{
28-
text: `Created the index \`${indexes[0]}\``,
35+
text: `Created the index "${indexes[0]}" on collection "${collection}" in database "${database}"`,
2936
type: "text",
3037
},
3138
],

src/tools/mongodb/metadata/connect.ts

+16-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
5-
import { ErrorCodes, MongoDBError } from "../../../errors.js";
65
import config from "../../../config.js";
76
import { MongoError as DriverError } from "mongodb";
87

@@ -37,25 +36,23 @@ export class ConnectTool extends MongoDBToolBase {
3736

3837
let connectionString: string;
3938

40-
if (typeof connectionStringOrClusterName === "string") {
41-
if (
42-
connectionStringOrClusterName.startsWith("mongodb://") ||
43-
connectionStringOrClusterName.startsWith("mongodb+srv://")
44-
) {
45-
connectionString = connectionStringOrClusterName;
46-
} else {
47-
// TODO:
48-
return {
49-
content: [
50-
{
51-
type: "text",
52-
text: `Connecting via cluster name not supported yet. Please provide a connection string.`,
53-
},
54-
],
55-
};
56-
}
39+
if (
40+
connectionStringOrClusterName.startsWith("mongodb://") ||
41+
connectionStringOrClusterName.startsWith("mongodb+srv://")
42+
) {
43+
connectionString = connectionStringOrClusterName;
5744
} else {
58-
throw new MongoDBError(ErrorCodes.InvalidParams, "Invalid connection options");
45+
// TODO: https://github.com/mongodb-js/mongodb-mcp-server/issues/19
46+
// We don't support connecting via cluster name since we'd need to obtain the user credentials
47+
// and fill in the connection string.
48+
return {
49+
content: [
50+
{
51+
type: "text",
52+
text: `Connecting via cluster name not supported yet. Please provide a connection string.`,
53+
},
54+
],
55+
};
5956
}
6057

6158
try {

src/tools/mongodb/mongodbTool.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@ export abstract class MongoDBToolBase extends ToolBase {
1919
protected category: ToolCategory = "mongodb";
2020

2121
protected async ensureConnected(): Promise<NodeDriverServiceProvider> {
22-
const provider = this.session.serviceProvider;
23-
if (!provider && config.connectionString) {
22+
if (!this.session.serviceProvider && config.connectionString) {
2423
await this.connectToMongoDB(config.connectionString);
2524
}
2625

27-
if (!provider) {
26+
if (!this.session.serviceProvider) {
2827
throw new MongoDBError(ErrorCodes.NotConnectedToMongoDB, "Not connected to MongoDB");
2928
}
3029

31-
return provider;
30+
return this.session.serviceProvider;
3231
}
3332

3433
protected handleError(error: unknown): Promise<CallToolResult> | CallToolResult {

tests/integration/helpers.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import path from "path";
66
import fs from "fs/promises";
77
import { Session } from "../../src/session.js";
88
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9-
import { MongoClient } from "mongodb";
9+
import { MongoClient, ObjectId } from "mongodb";
1010
import { toIncludeAllMembers } from "jest-extended";
11+
import config from "../../src/config.js";
1112

1213
interface ParameterInfo {
1314
name: string;
@@ -23,13 +24,16 @@ export function setupIntegrationTest(): {
2324
mongoClient: () => MongoClient;
2425
connectionString: () => string;
2526
connectMcpClient: () => Promise<void>;
27+
randomDbName: () => string;
2628
} {
2729
let mongoCluster: runner.MongoCluster | undefined;
2830
let mongoClient: MongoClient | undefined;
2931

3032
let mcpClient: Client | undefined;
3133
let mcpServer: Server | undefined;
3234

35+
let randomDbName: string;
36+
3337
beforeEach(async () => {
3438
const clientTransport = new InMemoryTransport();
3539
const serverTransport = new InMemoryTransport();
@@ -59,6 +63,7 @@ export function setupIntegrationTest(): {
5963
});
6064
await mcpServer.connect(serverTransport);
6165
await mcpClient.connect(clientTransport);
66+
randomDbName = new ObjectId().toString();
6267
});
6368

6469
afterEach(async () => {
@@ -70,6 +75,8 @@ export function setupIntegrationTest(): {
7075

7176
await mongoClient?.close();
7277
mongoClient = undefined;
78+
79+
config.connectionString = undefined;
7380
});
7481

7582
beforeAll(async function () {
@@ -144,6 +151,7 @@ export function setupIntegrationTest(): {
144151
arguments: { connectionStringOrClusterName: getConnectionString() },
145152
});
146153
},
154+
randomDbName: () => randomDbName,
147155
};
148156
}
149157

tests/integration/tools/mongodb/create/createCollection.test.ts

+43-21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { toIncludeSameMembers } from "jest-extended";
88
import { McpError } from "@modelcontextprotocol/sdk/types.js";
99
import { ObjectId } from "bson";
10+
import config from "../../../../../src/config.js";
1011

1112
describe("createCollection tool", () => {
1213
const integration = setupIntegrationTest();
@@ -48,69 +49,90 @@ describe("createCollection tool", () => {
4849
describe("with non-existent database", () => {
4950
it("creates a new collection", async () => {
5051
const mongoClient = integration.mongoClient();
51-
let collections = await mongoClient.db("foo").listCollections().toArray();
52+
let collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
5253
expect(collections).toHaveLength(0);
5354

5455
await integration.connectMcpClient();
5556
const response = await integration.mcpClient().callTool({
5657
name: "create-collection",
57-
arguments: { database: "foo", collection: "bar" },
58+
arguments: { database: integration.randomDbName(), collection: "bar" },
5859
});
5960
const content = getResponseContent(response.content);
60-
expect(content).toEqual('Collection "bar" created in database "foo".');
61+
expect(content).toEqual(`Collection "bar" created in database "${integration.randomDbName()}".`);
6162

62-
collections = await mongoClient.db("foo").listCollections().toArray();
63+
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
6364
expect(collections).toHaveLength(1);
6465
expect(collections[0].name).toEqual("bar");
6566
});
6667
});
6768

6869
describe("with existing database", () => {
69-
let dbName: string;
70-
beforeEach(() => {
71-
dbName = new ObjectId().toString();
72-
});
73-
7470
it("creates new collection", async () => {
7571
const mongoClient = integration.mongoClient();
76-
await mongoClient.db(dbName).createCollection("collection1");
77-
let collections = await mongoClient.db(dbName).listCollections().toArray();
72+
await mongoClient.db(integration.randomDbName()).createCollection("collection1");
73+
let collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
7874
expect(collections).toHaveLength(1);
7975

8076
await integration.connectMcpClient();
8177
const response = await integration.mcpClient().callTool({
8278
name: "create-collection",
83-
arguments: { database: dbName, collection: "collection2" },
79+
arguments: { database: integration.randomDbName(), collection: "collection2" },
8480
});
8581
const content = getResponseContent(response.content);
86-
expect(content).toEqual(`Collection "collection2" created in database "${dbName}".`);
87-
collections = await mongoClient.db(dbName).listCollections().toArray();
82+
expect(content).toEqual(`Collection "collection2" created in database "${integration.randomDbName()}".`);
83+
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
8884
expect(collections).toHaveLength(2);
8985
expect(collections.map((c) => c.name)).toIncludeSameMembers(["collection1", "collection2"]);
9086
});
9187

9288
it("does nothing if collection already exists", async () => {
9389
const mongoClient = integration.mongoClient();
94-
await mongoClient.db(dbName).collection("collection1").insertOne({});
95-
let collections = await mongoClient.db(dbName).listCollections().toArray();
90+
await mongoClient.db(integration.randomDbName()).collection("collection1").insertOne({});
91+
let collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
9692
expect(collections).toHaveLength(1);
97-
let documents = await mongoClient.db(dbName).collection("collection1").find({}).toArray();
93+
let documents = await mongoClient
94+
.db(integration.randomDbName())
95+
.collection("collection1")
96+
.find({})
97+
.toArray();
9898
expect(documents).toHaveLength(1);
9999

100100
await integration.connectMcpClient();
101101
const response = await integration.mcpClient().callTool({
102102
name: "create-collection",
103-
arguments: { database: dbName, collection: "collection1" },
103+
arguments: { database: integration.randomDbName(), collection: "collection1" },
104104
});
105105
const content = getResponseContent(response.content);
106-
expect(content).toEqual(`Collection "collection1" created in database "${dbName}".`);
107-
collections = await mongoClient.db(dbName).listCollections().toArray();
106+
expect(content).toEqual(`Collection "collection1" created in database "${integration.randomDbName()}".`);
107+
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
108108
expect(collections).toHaveLength(1);
109109
expect(collections[0].name).toEqual("collection1");
110110

111111
// Make sure we didn't drop the existing collection
112-
documents = await mongoClient.db(dbName).collection("collection1").find({}).toArray();
112+
documents = await mongoClient.db(integration.randomDbName()).collection("collection1").find({}).toArray();
113113
expect(documents).toHaveLength(1);
114114
});
115115
});
116+
117+
describe("when not connected", () => {
118+
it("connects automatically if connection string is configured", async () => {
119+
config.connectionString = integration.connectionString();
120+
121+
const response = await integration.mcpClient().callTool({
122+
name: "create-collection",
123+
arguments: { database: integration.randomDbName(), collection: "new-collection" },
124+
});
125+
const content = getResponseContent(response.content);
126+
expect(content).toEqual(`Collection "new-collection" created in database "${integration.randomDbName()}".`);
127+
});
128+
129+
it("throw an error if connection string is not configured", async () => {
130+
const response = await integration.mcpClient().callTool({
131+
name: "create-collection",
132+
arguments: { database: integration.randomDbName(), collection: "new-collection" },
133+
});
134+
const content = getResponseContent(response.content);
135+
expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
136+
});
137+
});
116138
});

0 commit comments

Comments
 (0)