mongodb-mcp-server 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.github/workflows/code_health.yaml +53 -4
  2. package/.github/workflows/prepare_release.yaml +4 -4
  3. package/README.md +2 -0
  4. package/dist/common/atlas/apiClient.js +30 -2
  5. package/dist/common/atlas/apiClient.js.map +1 -1
  6. package/dist/config.js +2 -7
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.js +10 -4
  9. package/dist/index.js.map +1 -1
  10. package/dist/logger.js +52 -27
  11. package/dist/logger.js.map +1 -1
  12. package/dist/packageInfo.js +6 -0
  13. package/dist/packageInfo.js.map +1 -0
  14. package/dist/server.js +71 -8
  15. package/dist/server.js.map +1 -1
  16. package/dist/session.js +17 -12
  17. package/dist/session.js.map +1 -1
  18. package/dist/telemetry/constants.js +15 -0
  19. package/dist/telemetry/constants.js.map +1 -0
  20. package/dist/telemetry/eventCache.js +53 -0
  21. package/dist/telemetry/eventCache.js.map +1 -0
  22. package/dist/telemetry/telemetry.js +96 -0
  23. package/dist/telemetry/telemetry.js.map +1 -0
  24. package/dist/telemetry/types.js +2 -0
  25. package/dist/telemetry/types.js.map +1 -0
  26. package/dist/tools/atlas/atlasTool.js +8 -3
  27. package/dist/tools/atlas/atlasTool.js.map +1 -1
  28. package/dist/tools/atlas/createAccessList.js +0 -1
  29. package/dist/tools/atlas/createAccessList.js.map +1 -1
  30. package/dist/tools/atlas/createDBUser.js +0 -1
  31. package/dist/tools/atlas/createDBUser.js.map +1 -1
  32. package/dist/tools/atlas/createFreeCluster.js +0 -1
  33. package/dist/tools/atlas/createFreeCluster.js.map +1 -1
  34. package/dist/tools/atlas/createProject.js +0 -1
  35. package/dist/tools/atlas/createProject.js.map +1 -1
  36. package/dist/tools/atlas/inspectAccessList.js +0 -1
  37. package/dist/tools/atlas/inspectAccessList.js.map +1 -1
  38. package/dist/tools/atlas/inspectCluster.js +0 -1
  39. package/dist/tools/atlas/inspectCluster.js.map +1 -1
  40. package/dist/tools/atlas/listClusters.js +0 -1
  41. package/dist/tools/atlas/listClusters.js.map +1 -1
  42. package/dist/tools/atlas/listDBUsers.js +0 -1
  43. package/dist/tools/atlas/listDBUsers.js.map +1 -1
  44. package/dist/tools/atlas/listOrgs.js +0 -1
  45. package/dist/tools/atlas/listOrgs.js.map +1 -1
  46. package/dist/tools/atlas/listProjects.js +10 -4
  47. package/dist/tools/atlas/listProjects.js.map +1 -1
  48. package/dist/tools/mongodb/metadata/collectionSchema.js +15 -13
  49. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
  50. package/dist/tools/mongodb/metadata/collectionStorageSize.js +32 -3
  51. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
  52. package/dist/tools/mongodb/metadata/connect.js +5 -6
  53. package/dist/tools/mongodb/metadata/connect.js.map +1 -1
  54. package/dist/tools/mongodb/metadata/dbStats.js +6 -1
  55. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
  56. package/dist/tools/mongodb/metadata/explain.js +14 -7
  57. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  58. package/dist/tools/mongodb/metadata/logs.js +45 -0
  59. package/dist/tools/mongodb/metadata/logs.js.map +1 -0
  60. package/dist/tools/mongodb/mongodbTool.js +10 -11
  61. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  62. package/dist/tools/mongodb/read/aggregate.js +3 -3
  63. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  64. package/dist/tools/mongodb/read/collectionIndexes.js +24 -5
  65. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
  66. package/dist/tools/mongodb/read/find.js +3 -2
  67. package/dist/tools/mongodb/read/find.js.map +1 -1
  68. package/dist/tools/mongodb/tools.js +2 -0
  69. package/dist/tools/mongodb/tools.js.map +1 -1
  70. package/dist/tools/mongodb/update/renameCollection.js +28 -4
  71. package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
  72. package/dist/tools/mongodb/update/updateMany.js +5 -7
  73. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  74. package/dist/tools/tool.js +39 -10
  75. package/dist/tools/tool.js.map +1 -1
  76. package/eslint.config.js +29 -10
  77. package/global.d.ts +1 -0
  78. package/package.json +6 -2
  79. package/scripts/apply.ts +9 -7
  80. package/scripts/filter.ts +3 -2
  81. package/src/common/atlas/apiClient.ts +48 -7
  82. package/src/config.ts +4 -10
  83. package/src/index.ts +10 -6
  84. package/src/logger.ts +66 -28
  85. package/src/packageInfo.ts +6 -0
  86. package/src/server.ts +103 -9
  87. package/src/session.ts +34 -17
  88. package/src/telemetry/constants.ts +15 -0
  89. package/src/telemetry/eventCache.ts +62 -0
  90. package/src/telemetry/telemetry.ts +137 -0
  91. package/src/telemetry/types.ts +60 -0
  92. package/src/tools/atlas/atlasTool.ts +7 -5
  93. package/src/tools/atlas/createAccessList.ts +0 -2
  94. package/src/tools/atlas/createDBUser.ts +0 -2
  95. package/src/tools/atlas/createFreeCluster.ts +0 -2
  96. package/src/tools/atlas/createProject.ts +0 -1
  97. package/src/tools/atlas/inspectAccessList.ts +0 -2
  98. package/src/tools/atlas/inspectCluster.ts +0 -2
  99. package/src/tools/atlas/listClusters.ts +0 -2
  100. package/src/tools/atlas/listDBUsers.ts +0 -2
  101. package/src/tools/atlas/listOrgs.ts +0 -2
  102. package/src/tools/atlas/listProjects.ts +12 -4
  103. package/src/tools/mongodb/metadata/collectionSchema.ts +16 -14
  104. package/src/tools/mongodb/metadata/collectionStorageSize.ts +41 -3
  105. package/src/tools/mongodb/metadata/connect.ts +5 -6
  106. package/src/tools/mongodb/metadata/dbStats.ts +6 -1
  107. package/src/tools/mongodb/metadata/explain.ts +20 -7
  108. package/src/tools/mongodb/metadata/logs.ts +55 -0
  109. package/src/tools/mongodb/mongodbTool.ts +12 -15
  110. package/src/tools/mongodb/read/aggregate.ts +3 -3
  111. package/src/tools/mongodb/read/collectionIndexes.ts +29 -5
  112. package/src/tools/mongodb/read/find.ts +3 -2
  113. package/src/tools/mongodb/tools.ts +2 -0
  114. package/src/tools/mongodb/update/renameCollection.ts +33 -4
  115. package/src/tools/mongodb/update/updateMany.ts +5 -7
  116. package/src/tools/tool.ts +51 -15
  117. package/tests/integration/helpers.ts +84 -107
  118. package/tests/integration/inMemoryTransport.ts +3 -2
  119. package/tests/integration/server.test.ts +47 -21
  120. package/tests/integration/tools/atlas/accessLists.test.ts +13 -15
  121. package/tests/integration/tools/atlas/atlasHelpers.ts +3 -8
  122. package/tests/integration/tools/atlas/clusters.test.ts +12 -13
  123. package/tests/integration/tools/atlas/dbUsers.test.ts +9 -9
  124. package/tests/integration/tools/atlas/orgs.test.ts +4 -4
  125. package/tests/integration/tools/atlas/projects.test.ts +10 -12
  126. package/tests/integration/tools/mongodb/create/createCollection.test.ts +19 -62
  127. package/tests/integration/tools/mongodb/create/createIndex.test.ts +41 -87
  128. package/tests/integration/tools/mongodb/create/insertMany.test.ts +35 -78
  129. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +25 -62
  130. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +22 -71
  131. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +29 -63
  132. package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +154 -0
  133. package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +86 -0
  134. package/tests/integration/tools/mongodb/metadata/connect.test.ts +33 -23
  135. package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +104 -0
  136. package/tests/integration/tools/mongodb/metadata/explain.test.ts +171 -0
  137. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +28 -56
  138. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +32 -26
  139. package/tests/integration/tools/mongodb/metadata/logs.test.ts +83 -0
  140. package/tests/integration/tools/mongodb/mongodbHelpers.ts +165 -0
  141. package/tests/integration/tools/mongodb/read/aggregate.test.ts +99 -0
  142. package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +99 -0
  143. package/tests/integration/tools/mongodb/read/count.test.ts +31 -79
  144. package/tests/integration/tools/mongodb/read/find.test.ts +182 -0
  145. package/tests/integration/tools/mongodb/update/renameCollection.test.ts +194 -0
  146. package/tests/integration/tools/mongodb/update/updateMany.test.ts +238 -0
  147. package/tsconfig.jest.json +2 -1
  148. package/tsconfig.json +1 -1
  149. package/tsconfig.lint.json +8 -0
@@ -1,17 +1,15 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
- import { setupIntegrationTest } from "../../helpers.js";
3
- import { Session } from "../../../../src/session.js";
4
2
  import { ObjectId } from "mongodb";
5
- import { parseTable, describeAtlas } from "./atlasHelpers.js";
3
+ import { parseTable, describeWithAtlas } from "./atlasHelpers.js";
4
+ import { expectDefined } from "../../helpers.js";
6
5
 
7
6
  const randomId = new ObjectId().toString();
8
7
 
9
- describeAtlas("projects", (integration) => {
8
+ describeWithAtlas("projects", (integration) => {
10
9
  const projName = "testProj-" + randomId;
11
10
 
12
11
  afterAll(async () => {
13
- const session: Session = integration.mcpServer().session;
14
- session.ensureAuthenticated();
12
+ const session = integration.mcpServer().session;
15
13
 
16
14
  const projects = await session.apiClient.listProjects();
17
15
  for (const project of projects?.results || []) {
@@ -31,10 +29,10 @@ describeAtlas("projects", (integration) => {
31
29
  describe("atlas-create-project", () => {
32
30
  it("should have correct metadata", async () => {
33
31
  const { tools } = await integration.mcpClient().listTools();
34
- const createProject = tools.find((tool) => tool.name === "atlas-create-project")!;
35
- expect(createProject).toBeDefined();
32
+ const createProject = tools.find((tool) => tool.name === "atlas-create-project");
33
+ expectDefined(createProject);
36
34
  expect(createProject.inputSchema.type).toBe("object");
37
- expect(createProject.inputSchema.properties).toBeDefined();
35
+ expectDefined(createProject.inputSchema.properties);
38
36
  expect(createProject.inputSchema.properties).toHaveProperty("projectName");
39
37
  expect(createProject.inputSchema.properties).toHaveProperty("organizationId");
40
38
  });
@@ -51,10 +49,10 @@ describeAtlas("projects", (integration) => {
51
49
  describe("atlas-list-projects", () => {
52
50
  it("should have correct metadata", async () => {
53
51
  const { tools } = await integration.mcpClient().listTools();
54
- const listProjects = tools.find((tool) => tool.name === "atlas-list-projects")!;
55
- expect(listProjects).toBeDefined();
52
+ const listProjects = tools.find((tool) => tool.name === "atlas-list-projects");
53
+ expectDefined(listProjects);
56
54
  expect(listProjects.inputSchema.type).toBe("object");
57
- expect(listProjects.inputSchema.properties).toBeDefined();
55
+ expectDefined(listProjects.inputSchema.properties);
58
56
  expect(listProjects.inputSchema.properties).toHaveProperty("orgId");
59
57
  });
60
58
 
@@ -1,50 +1,22 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
1
3
  import {
2
4
  getResponseContent,
3
- validateParameters,
4
- dbOperationParameters,
5
- setupIntegrationTest,
5
+ databaseCollectionParameters,
6
+ validateToolMetadata,
7
+ validateThrowsForInvalidArguments,
8
+ databaseCollectionInvalidArgs,
6
9
  } from "../../../helpers.js";
7
- import { toIncludeSameMembers } from "jest-extended";
8
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
9
- import { ObjectId } from "bson";
10
- import config from "../../../../../src/config.js";
11
-
12
- describe("createCollection tool", () => {
13
- const integration = setupIntegrationTest();
14
10
 
15
- it("should have correct metadata", async () => {
16
- const { tools } = await integration.mcpClient().listTools();
17
- const listCollections = tools.find((tool) => tool.name === "create-collection")!;
18
- expect(listCollections).toBeDefined();
19
- expect(listCollections.description).toBe(
20
- "Creates a new collection in a database. If the database doesn't exist, it will be created automatically."
21
- );
11
+ describeWithMongoDB("createCollection tool", (integration) => {
12
+ validateToolMetadata(
13
+ integration,
14
+ "create-collection",
15
+ "Creates a new collection in a database. If the database doesn't exist, it will be created automatically.",
16
+ databaseCollectionParameters
17
+ );
22
18
 
23
- validateParameters(listCollections, dbOperationParameters);
24
- });
25
-
26
- describe("with invalid arguments", () => {
27
- const args = [
28
- {},
29
- { database: 123, collection: "bar" },
30
- { foo: "bar", database: "test", collection: "bar" },
31
- { collection: [], database: "test" },
32
- ];
33
- for (const arg of args) {
34
- it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
35
- await integration.connectMcpClient();
36
- try {
37
- await integration.mcpClient().callTool({ name: "create-collection", arguments: arg });
38
- expect.fail("Expected an error to be thrown");
39
- } catch (error) {
40
- expect(error).toBeInstanceOf(McpError);
41
- const mcpError = error as McpError;
42
- expect(mcpError.code).toEqual(-32602);
43
- expect(mcpError.message).toContain("Invalid arguments for tool create-collection");
44
- }
45
- });
46
- }
47
- });
19
+ validateThrowsForInvalidArguments(integration, "create-collection", databaseCollectionInvalidArgs);
48
20
 
49
21
  describe("with non-existent database", () => {
50
22
  it("creates a new collection", async () => {
@@ -114,25 +86,10 @@ describe("createCollection tool", () => {
114
86
  });
115
87
  });
116
88
 
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("throws 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
- });
89
+ validateAutoConnectBehavior(integration, "create-collection", () => {
90
+ return {
91
+ args: { database: integration.randomDbName(), collection: "new-collection" },
92
+ expectedResponse: `Collection "new-collection" created in database "${integration.randomDbName()}".`,
93
+ };
137
94
  });
138
95
  });
@@ -1,63 +1,38 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
1
3
  import {
2
4
  getResponseContent,
3
- validateParameters,
4
- dbOperationParameters,
5
- setupIntegrationTest,
5
+ databaseCollectionParameters,
6
+ validateToolMetadata,
7
+ validateThrowsForInvalidArguments,
8
+ expectDefined,
6
9
  } from "../../../helpers.js";
7
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
10
  import { IndexDirection } from "mongodb";
9
- import config from "../../../../../src/config.js";
10
-
11
- describe("createIndex tool", () => {
12
- const integration = setupIntegrationTest();
13
-
14
- it("should have correct metadata", async () => {
15
- const { tools } = await integration.mcpClient().listTools();
16
- const createIndex = tools.find((tool) => tool.name === "create-index")!;
17
- expect(createIndex).toBeDefined();
18
- expect(createIndex.description).toBe("Create an index for a collection");
19
-
20
- validateParameters(createIndex, [
21
- ...dbOperationParameters,
22
- {
23
- name: "keys",
24
- type: "object",
25
- description: "The index definition",
26
- required: true,
27
- },
28
- {
29
- name: "name",
30
- type: "string",
31
- description: "The name of the index",
32
- required: false,
33
- },
34
- ]);
35
- });
36
11
 
37
- describe("with invalid arguments", () => {
38
- const args = [
39
- {},
40
- { collection: "bar", database: 123, keys: { foo: 1 } },
41
- { collection: "bar", database: "test", keys: { foo: 5 } },
42
- { collection: [], database: "test", keys: { foo: 1 } },
43
- { collection: "bar", database: "test", keys: { foo: 1 }, name: 123 },
44
- { collection: "bar", database: "test", keys: "foo", name: "my-index" },
45
- ];
46
- for (const arg of args) {
47
- it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
48
- await integration.connectMcpClient();
49
- try {
50
- await integration.mcpClient().callTool({ name: "create-index", arguments: arg });
51
- expect.fail("Expected an error to be thrown");
52
- } catch (error) {
53
- expect(error).toBeInstanceOf(McpError);
54
- const mcpError = error as McpError;
55
- expect(mcpError.code).toEqual(-32602);
56
- expect(mcpError.message).toContain("Invalid arguments for tool create-index");
57
- }
58
- });
59
- }
60
- });
12
+ describeWithMongoDB("createIndex tool", (integration) => {
13
+ validateToolMetadata(integration, "create-index", "Create an index for a collection", [
14
+ ...databaseCollectionParameters,
15
+ {
16
+ name: "keys",
17
+ type: "object",
18
+ description: "The index definition",
19
+ required: true,
20
+ },
21
+ {
22
+ name: "name",
23
+ type: "string",
24
+ description: "The name of the index",
25
+ required: false,
26
+ },
27
+ ]);
28
+
29
+ validateThrowsForInvalidArguments(integration, "create-index", [
30
+ {},
31
+ { collection: "bar", database: 123, keys: { foo: 1 } },
32
+ { collection: [], database: "test", keys: { foo: 1 } },
33
+ { collection: "bar", database: "test", keys: { foo: 1 }, name: 123 },
34
+ { collection: "bar", database: "test", keys: "foo", name: "my-index" },
35
+ ]);
61
36
 
62
37
  const validateIndex = async (collection: string, expected: { name: string; key: object }[]) => {
63
38
  const mongoClient = integration.mongoClient();
@@ -69,8 +44,8 @@ describe("createIndex tool", () => {
69
44
  expect(indexes[0].name).toEqual("_id_");
70
45
  for (const index of expected) {
71
46
  const foundIndex = indexes.find((i) => i.name === index.name);
72
- expect(foundIndex).toBeDefined();
73
- expect(foundIndex!.key).toEqual(index.key);
47
+ expectDefined(foundIndex);
48
+ expect(foundIndex.key).toEqual(index.key);
74
49
  }
75
50
  };
76
51
 
@@ -215,35 +190,14 @@ describe("createIndex tool", () => {
215
190
  });
216
191
  }
217
192
 
218
- describe("when not connected", () => {
219
- it("connects automatically if connection string is configured", async () => {
220
- config.connectionString = integration.connectionString();
221
-
222
- const response = await integration.mcpClient().callTool({
223
- name: "create-index",
224
- arguments: {
225
- database: integration.randomDbName(),
226
- collection: "coll1",
227
- keys: { prop1: 1 },
228
- },
229
- });
230
- const content = getResponseContent(response.content);
231
- expect(content).toEqual(
232
- `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
233
- );
234
- });
235
-
236
- it("throws an error if connection string is not configured", async () => {
237
- const response = await integration.mcpClient().callTool({
238
- name: "create-index",
239
- arguments: {
240
- database: integration.randomDbName(),
241
- collection: "coll1",
242
- keys: { prop1: 1 },
243
- },
244
- });
245
- const content = getResponseContent(response.content);
246
- expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
247
- });
193
+ validateAutoConnectBehavior(integration, "create-index", () => {
194
+ return {
195
+ args: {
196
+ database: integration.randomDbName(),
197
+ collection: "coll1",
198
+ keys: { prop1: 1 },
199
+ },
200
+ expectedResponse: `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`,
201
+ };
248
202
  });
249
203
  });
@@ -1,60 +1,36 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
1
3
  import {
2
4
  getResponseContent,
3
- validateParameters,
4
- dbOperationParameters,
5
- setupIntegrationTest,
5
+ databaseCollectionParameters,
6
+ validateToolMetadata,
7
+ validateThrowsForInvalidArguments,
8
+ expectDefined,
6
9
  } from "../../../helpers.js";
7
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
- import config from "../../../../../src/config.js";
9
-
10
- describe("insertMany tool", () => {
11
- const integration = setupIntegrationTest();
12
-
13
- it("should have correct metadata", async () => {
14
- const { tools } = await integration.mcpClient().listTools();
15
- const insertMany = tools.find((tool) => tool.name === "insert-many")!;
16
- expect(insertMany).toBeDefined();
17
- expect(insertMany.description).toBe("Insert an array of documents into a MongoDB collection");
18
-
19
- validateParameters(insertMany, [
20
- ...dbOperationParameters,
21
- {
22
- name: "documents",
23
- type: "array",
24
- description:
25
- "The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()",
26
- required: true,
27
- },
28
- ]);
29
- });
30
10
 
31
- describe("with invalid arguments", () => {
32
- const args = [
33
- {},
34
- { collection: "bar", database: 123, documents: [] },
35
- { collection: [], database: "test", documents: [] },
36
- { collection: "bar", database: "test", documents: "my-document" },
37
- { collection: "bar", database: "test", documents: { name: "Peter" } },
38
- ];
39
- for (const arg of args) {
40
- it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
41
- await integration.connectMcpClient();
42
- try {
43
- await integration.mcpClient().callTool({ name: "insert-many", arguments: arg });
44
- expect.fail("Expected an error to be thrown");
45
- } catch (error) {
46
- expect(error).toBeInstanceOf(McpError);
47
- const mcpError = error as McpError;
48
- expect(mcpError.code).toEqual(-32602);
49
- expect(mcpError.message).toContain("Invalid arguments for tool insert-many");
50
- }
51
- });
52
- }
53
- });
11
+ describeWithMongoDB("insertMany tool", (integration) => {
12
+ validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
13
+ ...databaseCollectionParameters,
14
+ {
15
+ name: "documents",
16
+ type: "array",
17
+ description:
18
+ "The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()",
19
+ required: true,
20
+ },
21
+ ]);
22
+
23
+ validateThrowsForInvalidArguments(integration, "insert-many", [
24
+ {},
25
+ { collection: "bar", database: 123, documents: [] },
26
+ { collection: [], database: "test", documents: [] },
27
+ { collection: "bar", database: "test", documents: "my-document" },
28
+ { collection: "bar", database: "test", documents: { name: "Peter" } },
29
+ ]);
54
30
 
55
31
  const validateDocuments = async (collection: string, expectedDocuments: object[]) => {
56
32
  const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
57
- expect(collections.find((c) => c.name === collection)).toBeDefined();
33
+ expectDefined(collections.find((c) => c.name === collection));
58
34
 
59
35
  const docs = await integration
60
36
  .mongoClient()
@@ -109,33 +85,14 @@ describe("insertMany tool", () => {
109
85
  expect(content).toContain(insertedIds[0].toString());
110
86
  });
111
87
 
112
- describe("when not connected", () => {
113
- it("connects automatically if connection string is configured", async () => {
114
- config.connectionString = integration.connectionString();
115
-
116
- const response = await integration.mcpClient().callTool({
117
- name: "insert-many",
118
- arguments: {
119
- database: integration.randomDbName(),
120
- collection: "coll1",
121
- documents: [{ prop1: "value1" }],
122
- },
123
- });
124
- const content = getResponseContent(response.content);
125
- expect(content).toContain('Inserted `1` document(s) into collection "coll1"');
126
- });
127
-
128
- it("throws an error if connection string is not configured", async () => {
129
- const response = await integration.mcpClient().callTool({
130
- name: "insert-many",
131
- arguments: {
132
- database: integration.randomDbName(),
133
- collection: "coll1",
134
- documents: [{ prop1: "value1" }],
135
- },
136
- });
137
- const content = getResponseContent(response.content);
138
- expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
139
- });
88
+ validateAutoConnectBehavior(integration, "insert-many", () => {
89
+ return {
90
+ args: {
91
+ database: integration.randomDbName(),
92
+ collection: "coll1",
93
+ documents: [{ prop1: "value1" }],
94
+ },
95
+ expectedResponse: 'Inserted `1` document(s) into collection "coll1"',
96
+ };
140
97
  });
141
98
  });
@@ -1,23 +1,19 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
1
3
  import {
2
4
  getResponseContent,
3
- validateParameters,
4
- dbOperationParameters,
5
- setupIntegrationTest,
5
+ databaseCollectionParameters,
6
+ validateToolMetadata,
7
+ validateThrowsForInvalidArguments,
6
8
  } from "../../../helpers.js";
7
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
- import config from "../../../../../src/config.js";
9
-
10
- describe("deleteMany tool", () => {
11
- const integration = setupIntegrationTest();
12
9
 
13
- it("should have correct metadata", async () => {
14
- const { tools } = await integration.mcpClient().listTools();
15
- const deleteMany = tools.find((tool) => tool.name === "delete-many")!;
16
- expect(deleteMany).toBeDefined();
17
- expect(deleteMany.description).toBe("Removes all documents that match the filter from a MongoDB collection");
18
-
19
- validateParameters(deleteMany, [
20
- ...dbOperationParameters,
10
+ describeWithMongoDB("deleteMany tool", (integration) => {
11
+ validateToolMetadata(
12
+ integration,
13
+ "delete-many",
14
+ "Removes all documents that match the filter from a MongoDB collection",
15
+ [
16
+ ...databaseCollectionParameters,
21
17
  {
22
18
  name: "filter",
23
19
  type: "object",
@@ -25,31 +21,17 @@ describe("deleteMany tool", () => {
25
21
  "The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()",
26
22
  required: false,
27
23
  },
28
- ]);
29
- });
24
+ ]
25
+ );
30
26
 
31
27
  describe("with invalid arguments", () => {
32
- const args = [
28
+ validateThrowsForInvalidArguments(integration, "delete-many", [
33
29
  {},
34
30
  { collection: "bar", database: 123, filter: {} },
35
31
  { collection: [], database: "test", filter: {} },
36
32
  { collection: "bar", database: "test", filter: "my-document" },
37
33
  { collection: "bar", database: "test", filter: [{ name: "Peter" }] },
38
- ];
39
- for (const arg of args) {
40
- it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
41
- await integration.connectMcpClient();
42
- try {
43
- await integration.mcpClient().callTool({ name: "delete-many", arguments: arg });
44
- expect.fail("Expected an error to be thrown");
45
- } catch (error) {
46
- expect(error).toBeInstanceOf(McpError);
47
- const mcpError = error as McpError;
48
- expect(mcpError.code).toEqual(-32602);
49
- expect(mcpError.message).toContain("Invalid arguments for tool delete-many");
50
- }
51
- });
52
- }
34
+ ]);
53
35
  });
54
36
 
55
37
  it("doesn't create the collection if it doesn't exist", async () => {
@@ -159,33 +141,14 @@ describe("deleteMany tool", () => {
159
141
  await validateDocuments([]);
160
142
  });
161
143
 
162
- describe("when not connected", () => {
163
- it("connects automatically if connection string is configured", async () => {
164
- config.connectionString = integration.connectionString();
165
-
166
- const response = await integration.mcpClient().callTool({
167
- name: "delete-many",
168
- arguments: {
169
- database: integration.randomDbName(),
170
- collection: "coll1",
171
- filter: {},
172
- },
173
- });
174
- const content = getResponseContent(response.content);
175
- expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
176
- });
177
-
178
- it("throws an error if connection string is not configured", async () => {
179
- const response = await integration.mcpClient().callTool({
180
- name: "delete-many",
181
- arguments: {
182
- database: integration.randomDbName(),
183
- collection: "coll1",
184
- filter: {},
185
- },
186
- });
187
- const content = getResponseContent(response.content);
188
- expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
189
- });
144
+ validateAutoConnectBehavior(integration, "delete-many", () => {
145
+ return {
146
+ args: {
147
+ database: integration.randomDbName(),
148
+ collection: "coll1",
149
+ filter: {},
150
+ },
151
+ expectedResponse: 'Deleted `0` document(s) from collection "coll1"',
152
+ };
190
153
  });
191
154
  });
@@ -1,48 +1,22 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
1
3
  import {
2
4
  getResponseContent,
3
- validateParameters,
4
- dbOperationParameters,
5
- setupIntegrationTest,
5
+ databaseCollectionParameters,
6
+ validateToolMetadata,
7
+ validateThrowsForInvalidArguments,
8
+ databaseCollectionInvalidArgs,
6
9
  } from "../../../helpers.js";
7
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
- import config from "../../../../../src/config.js";
9
-
10
- describe("dropCollection tool", () => {
11
- const integration = setupIntegrationTest();
12
10
 
13
- it("should have correct metadata", async () => {
14
- const { tools } = await integration.mcpClient().listTools();
15
- const dropCollection = tools.find((tool) => tool.name === "drop-collection")!;
16
- expect(dropCollection).toBeDefined();
17
- expect(dropCollection.description).toBe(
18
- "Removes a collection or view from the database. The method also removes any indexes associated with the dropped collection."
19
- );
11
+ describeWithMongoDB("dropCollection tool", (integration) => {
12
+ validateToolMetadata(
13
+ integration,
14
+ "drop-collection",
15
+ "Removes a collection or view from the database. The method also removes any indexes associated with the dropped collection.",
16
+ databaseCollectionParameters
17
+ );
20
18
 
21
- validateParameters(dropCollection, [...dbOperationParameters]);
22
- });
23
-
24
- describe("with invalid arguments", () => {
25
- const args = [
26
- {},
27
- { database: 123, collection: "bar" },
28
- { foo: "bar", database: "test", collection: "bar" },
29
- { collection: [], database: "test" },
30
- ];
31
- for (const arg of args) {
32
- it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
33
- await integration.connectMcpClient();
34
- try {
35
- await integration.mcpClient().callTool({ name: "drop-collection", arguments: arg });
36
- expect.fail("Expected an error to be thrown");
37
- } catch (error) {
38
- expect(error).toBeInstanceOf(McpError);
39
- const mcpError = error as McpError;
40
- expect(mcpError.code).toEqual(-32602);
41
- expect(mcpError.message).toContain("Invalid arguments for tool drop-collection");
42
- }
43
- });
44
- }
45
- });
19
+ validateThrowsForInvalidArguments(integration, "drop-collection", databaseCollectionInvalidArgs);
46
20
 
47
21
  it("can drop non-existing collection", async () => {
48
22
  await integration.connectMcpClient();
@@ -83,36 +57,13 @@ describe("dropCollection tool", () => {
83
57
  expect(collections[0].name).toBe("coll2");
84
58
  });
85
59
 
86
- describe("when not connected", () => {
87
- it("connects automatically if connection string is configured", async () => {
88
- await integration.connectMcpClient();
89
- await integration.mongoClient().db(integration.randomDbName()).createCollection("coll1");
90
-
91
- config.connectionString = integration.connectionString();
92
-
93
- const response = await integration.mcpClient().callTool({
94
- name: "drop-collection",
95
- arguments: {
96
- database: integration.randomDbName(),
97
- collection: "coll1",
98
- },
99
- });
100
- const content = getResponseContent(response.content);
101
- expect(content).toContain(
102
- `Successfully dropped collection "coll1" from database "${integration.randomDbName()}"`
103
- );
104
- });
105
-
106
- it("throws an error if connection string is not configured", async () => {
107
- const response = await integration.mcpClient().callTool({
108
- name: "drop-collection",
109
- arguments: {
110
- database: integration.randomDbName(),
111
- collection: "coll1",
112
- },
113
- });
114
- const content = getResponseContent(response.content);
115
- expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
116
- });
60
+ validateAutoConnectBehavior(integration, "drop-collection", () => {
61
+ return {
62
+ args: {
63
+ database: integration.randomDbName(),
64
+ collection: "coll1",
65
+ },
66
+ expectedResponse: `Successfully dropped collection "coll1" from database "${integration.randomDbName()}"`,
67
+ };
117
68
  });
118
69
  });