mongodb-mcp-server 0.0.4

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 (164) hide show
  1. package/.github/workflows/code_health.yaml +65 -0
  2. package/.github/workflows/prepare_release.yaml +45 -0
  3. package/.github/workflows/publish.yaml +70 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc.json +37 -0
  6. package/.vscode/launch.json +17 -0
  7. package/CONTRIBUTING.md +185 -0
  8. package/LICENSE +202 -0
  9. package/README.md +234 -0
  10. package/dist/common/atlas/apiClient.js +147 -0
  11. package/dist/common/atlas/apiClient.js.map +1 -0
  12. package/dist/common/atlas/apiClientError.js +17 -0
  13. package/dist/common/atlas/apiClientError.js.map +1 -0
  14. package/dist/config.js +85 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/errors.js +12 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.js +26 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/logger.js +97 -0
  21. package/dist/logger.js.map +1 -0
  22. package/dist/server.js +45 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/session.js +30 -0
  25. package/dist/session.js.map +1 -0
  26. package/dist/tools/atlas/atlasTool.js +9 -0
  27. package/dist/tools/atlas/atlasTool.js.map +1 -0
  28. package/dist/tools/atlas/createAccessList.js +64 -0
  29. package/dist/tools/atlas/createAccessList.js.map +1 -0
  30. package/dist/tools/atlas/createDBUser.js +58 -0
  31. package/dist/tools/atlas/createDBUser.js.map +1 -0
  32. package/dist/tools/atlas/createFreeCluster.js +51 -0
  33. package/dist/tools/atlas/createFreeCluster.js.map +1 -0
  34. package/dist/tools/atlas/createProject.js +53 -0
  35. package/dist/tools/atlas/createProject.js.map +1 -0
  36. package/dist/tools/atlas/inspectAccessList.js +41 -0
  37. package/dist/tools/atlas/inspectAccessList.js.map +1 -0
  38. package/dist/tools/atlas/inspectCluster.js +42 -0
  39. package/dist/tools/atlas/inspectCluster.js.map +1 -0
  40. package/dist/tools/atlas/listClusters.js +97 -0
  41. package/dist/tools/atlas/listClusters.js.map +1 -0
  42. package/dist/tools/atlas/listDBUsers.js +52 -0
  43. package/dist/tools/atlas/listDBUsers.js.map +1 -0
  44. package/dist/tools/atlas/listOrgs.js +30 -0
  45. package/dist/tools/atlas/listOrgs.js.map +1 -0
  46. package/dist/tools/atlas/listProjects.js +42 -0
  47. package/dist/tools/atlas/listProjects.js.map +1 -0
  48. package/dist/tools/atlas/tools.js +23 -0
  49. package/dist/tools/atlas/tools.js.map +1 -0
  50. package/dist/tools/mongodb/create/createCollection.js +23 -0
  51. package/dist/tools/mongodb/create/createCollection.js.map +1 -0
  52. package/dist/tools/mongodb/create/createIndex.js +33 -0
  53. package/dist/tools/mongodb/create/createIndex.js.map +1 -0
  54. package/dist/tools/mongodb/create/insertMany.js +33 -0
  55. package/dist/tools/mongodb/create/insertMany.js.map +1 -0
  56. package/dist/tools/mongodb/delete/deleteMany.js +31 -0
  57. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -0
  58. package/dist/tools/mongodb/delete/dropCollection.js +25 -0
  59. package/dist/tools/mongodb/delete/dropCollection.js.map +1 -0
  60. package/dist/tools/mongodb/delete/dropDatabase.js +25 -0
  61. package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -0
  62. package/dist/tools/mongodb/metadata/collectionSchema.js +38 -0
  63. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -0
  64. package/dist/tools/mongodb/metadata/collectionStorageSize.js +28 -0
  65. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -0
  66. package/dist/tools/mongodb/metadata/connect.js +86 -0
  67. package/dist/tools/mongodb/metadata/connect.js.map +1 -0
  68. package/dist/tools/mongodb/metadata/dbStats.js +28 -0
  69. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -0
  70. package/dist/tools/mongodb/metadata/explain.js +77 -0
  71. package/dist/tools/mongodb/metadata/explain.js.map +1 -0
  72. package/dist/tools/mongodb/metadata/listCollections.js +35 -0
  73. package/dist/tools/mongodb/metadata/listCollections.js.map +1 -0
  74. package/dist/tools/mongodb/metadata/listDatabases.js +23 -0
  75. package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -0
  76. package/dist/tools/mongodb/mongodbTool.js +58 -0
  77. package/dist/tools/mongodb/mongodbTool.js.map +1 -0
  78. package/dist/tools/mongodb/read/aggregate.js +38 -0
  79. package/dist/tools/mongodb/read/aggregate.js.map +1 -0
  80. package/dist/tools/mongodb/read/collectionIndexes.js +23 -0
  81. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -0
  82. package/dist/tools/mongodb/read/count.js +34 -0
  83. package/dist/tools/mongodb/read/count.js.map +1 -0
  84. package/dist/tools/mongodb/read/find.js +51 -0
  85. package/dist/tools/mongodb/read/find.js.map +1 -0
  86. package/dist/tools/mongodb/tools.js +41 -0
  87. package/dist/tools/mongodb/tools.js.map +1 -0
  88. package/dist/tools/mongodb/update/renameCollection.js +31 -0
  89. package/dist/tools/mongodb/update/renameCollection.js.map +1 -0
  90. package/dist/tools/mongodb/update/updateMany.js +56 -0
  91. package/dist/tools/mongodb/update/updateMany.js.map +1 -0
  92. package/dist/tools/tool.js +56 -0
  93. package/dist/tools/tool.js.map +1 -0
  94. package/eslint.config.js +35 -0
  95. package/jest.config.js +22 -0
  96. package/package.json +76 -0
  97. package/scripts/apply.ts +129 -0
  98. package/scripts/filter.ts +67 -0
  99. package/scripts/generate.sh +11 -0
  100. package/src/common/atlas/apiClient.ts +202 -0
  101. package/src/common/atlas/apiClientError.ts +21 -0
  102. package/src/common/atlas/openapi.d.ts +5849 -0
  103. package/src/config.ts +124 -0
  104. package/src/errors.ts +13 -0
  105. package/src/index.ts +30 -0
  106. package/src/logger.ts +117 -0
  107. package/src/server.ts +64 -0
  108. package/src/session.ts +37 -0
  109. package/src/tools/atlas/atlasTool.ts +10 -0
  110. package/src/tools/atlas/createAccessList.ts +78 -0
  111. package/src/tools/atlas/createDBUser.ts +70 -0
  112. package/src/tools/atlas/createFreeCluster.ts +55 -0
  113. package/src/tools/atlas/createProject.ts +63 -0
  114. package/src/tools/atlas/inspectAccessList.ts +44 -0
  115. package/src/tools/atlas/inspectCluster.ts +47 -0
  116. package/src/tools/atlas/listClusters.ts +104 -0
  117. package/src/tools/atlas/listDBUsers.ts +62 -0
  118. package/src/tools/atlas/listOrgs.ts +34 -0
  119. package/src/tools/atlas/listProjects.ts +46 -0
  120. package/src/tools/atlas/tools.ts +23 -0
  121. package/src/tools/mongodb/create/createCollection.ts +26 -0
  122. package/src/tools/mongodb/create/createIndex.ts +41 -0
  123. package/src/tools/mongodb/create/insertMany.ts +40 -0
  124. package/src/tools/mongodb/delete/deleteMany.ts +38 -0
  125. package/src/tools/mongodb/delete/dropCollection.ts +27 -0
  126. package/src/tools/mongodb/delete/dropDatabase.ts +26 -0
  127. package/src/tools/mongodb/metadata/collectionSchema.ts +41 -0
  128. package/src/tools/mongodb/metadata/collectionStorageSize.ts +30 -0
  129. package/src/tools/mongodb/metadata/connect.ts +94 -0
  130. package/src/tools/mongodb/metadata/dbStats.ts +30 -0
  131. package/src/tools/mongodb/metadata/explain.ts +90 -0
  132. package/src/tools/mongodb/metadata/listCollections.ts +38 -0
  133. package/src/tools/mongodb/metadata/listDatabases.ts +26 -0
  134. package/src/tools/mongodb/mongodbTool.ts +69 -0
  135. package/src/tools/mongodb/read/aggregate.ts +45 -0
  136. package/src/tools/mongodb/read/collectionIndexes.ts +24 -0
  137. package/src/tools/mongodb/read/count.ts +39 -0
  138. package/src/tools/mongodb/read/find.ts +62 -0
  139. package/src/tools/mongodb/tools.ts +41 -0
  140. package/src/tools/mongodb/update/renameCollection.ts +37 -0
  141. package/src/tools/mongodb/update/updateMany.ts +65 -0
  142. package/src/tools/tool.ts +90 -0
  143. package/src/types/mongodb-redact.d.ts +4 -0
  144. package/tests/integration/helpers.ts +241 -0
  145. package/tests/integration/inMemoryTransport.ts +58 -0
  146. package/tests/integration/server.test.ts +35 -0
  147. package/tests/integration/tools/atlas/accessLists.test.ts +100 -0
  148. package/tests/integration/tools/atlas/atlasHelpers.ts +110 -0
  149. package/tests/integration/tools/atlas/clusters.test.ts +122 -0
  150. package/tests/integration/tools/atlas/dbUsers.test.ts +80 -0
  151. package/tests/integration/tools/atlas/orgs.test.ts +24 -0
  152. package/tests/integration/tools/atlas/projects.test.ts +80 -0
  153. package/tests/integration/tools/mongodb/create/createCollection.test.ts +138 -0
  154. package/tests/integration/tools/mongodb/create/createIndex.test.ts +249 -0
  155. package/tests/integration/tools/mongodb/create/insertMany.test.ts +141 -0
  156. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +191 -0
  157. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +118 -0
  158. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +114 -0
  159. package/tests/integration/tools/mongodb/metadata/connect.test.ts +137 -0
  160. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +104 -0
  161. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +67 -0
  162. package/tests/integration/tools/mongodb/read/count.test.ts +138 -0
  163. package/tsconfig.jest.json +10 -0
  164. package/tsconfig.json +19 -0
@@ -0,0 +1,249 @@
1
+ import {
2
+ getResponseContent,
3
+ validateParameters,
4
+ dbOperationParameters,
5
+ setupIntegrationTest,
6
+ } from "../../../helpers.js";
7
+ import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
+ 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
+
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
+ });
61
+
62
+ const validateIndex = async (collection: string, expected: { name: string; key: object }[]) => {
63
+ const mongoClient = integration.mongoClient();
64
+ const collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
65
+ expect(collections).toHaveLength(1);
66
+ expect(collections[0].name).toEqual("coll1");
67
+ const indexes = await mongoClient.db(integration.randomDbName()).collection(collection).indexes();
68
+ expect(indexes).toHaveLength(expected.length + 1);
69
+ expect(indexes[0].name).toEqual("_id_");
70
+ for (const index of expected) {
71
+ const foundIndex = indexes.find((i) => i.name === index.name);
72
+ expect(foundIndex).toBeDefined();
73
+ expect(foundIndex!.key).toEqual(index.key);
74
+ }
75
+ };
76
+
77
+ it("creates the namespace if necessary", async () => {
78
+ await integration.connectMcpClient();
79
+ const response = await integration.mcpClient().callTool({
80
+ name: "create-index",
81
+ arguments: {
82
+ database: integration.randomDbName(),
83
+ collection: "coll1",
84
+ keys: { prop1: 1 },
85
+ name: "my-index",
86
+ },
87
+ });
88
+
89
+ const content = getResponseContent(response.content);
90
+ expect(content).toEqual(
91
+ `Created the index "my-index" on collection "coll1" in database "${integration.randomDbName()}"`
92
+ );
93
+
94
+ await validateIndex("coll1", [{ name: "my-index", key: { prop1: 1 } }]);
95
+ });
96
+
97
+ it("generates a name if not provided", async () => {
98
+ await integration.connectMcpClient();
99
+ const response = await integration.mcpClient().callTool({
100
+ name: "create-index",
101
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: 1 } },
102
+ });
103
+
104
+ const content = getResponseContent(response.content);
105
+ expect(content).toEqual(
106
+ `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
107
+ );
108
+ await validateIndex("coll1", [{ name: "prop1_1", key: { prop1: 1 } }]);
109
+ });
110
+
111
+ it("can create multiple indexes in the same collection", async () => {
112
+ await integration.connectMcpClient();
113
+ let response = await integration.mcpClient().callTool({
114
+ name: "create-index",
115
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: 1 } },
116
+ });
117
+
118
+ expect(getResponseContent(response.content)).toEqual(
119
+ `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
120
+ );
121
+
122
+ response = await integration.mcpClient().callTool({
123
+ name: "create-index",
124
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop2: -1 } },
125
+ });
126
+
127
+ expect(getResponseContent(response.content)).toEqual(
128
+ `Created the index "prop2_-1" on collection "coll1" in database "${integration.randomDbName()}"`
129
+ );
130
+
131
+ await validateIndex("coll1", [
132
+ { name: "prop1_1", key: { prop1: 1 } },
133
+ { name: "prop2_-1", key: { prop2: -1 } },
134
+ ]);
135
+ });
136
+
137
+ it("can create multiple indexes on the same property", async () => {
138
+ await integration.connectMcpClient();
139
+ let response = await integration.mcpClient().callTool({
140
+ name: "create-index",
141
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: 1 } },
142
+ });
143
+
144
+ expect(getResponseContent(response.content)).toEqual(
145
+ `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
146
+ );
147
+
148
+ response = await integration.mcpClient().callTool({
149
+ name: "create-index",
150
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: -1 } },
151
+ });
152
+
153
+ expect(getResponseContent(response.content)).toEqual(
154
+ `Created the index "prop1_-1" on collection "coll1" in database "${integration.randomDbName()}"`
155
+ );
156
+
157
+ await validateIndex("coll1", [
158
+ { name: "prop1_1", key: { prop1: 1 } },
159
+ { name: "prop1_-1", key: { prop1: -1 } },
160
+ ]);
161
+ });
162
+
163
+ it("doesn't duplicate indexes", async () => {
164
+ await integration.connectMcpClient();
165
+ let response = await integration.mcpClient().callTool({
166
+ name: "create-index",
167
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: 1 } },
168
+ });
169
+
170
+ expect(getResponseContent(response.content)).toEqual(
171
+ `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
172
+ );
173
+
174
+ response = await integration.mcpClient().callTool({
175
+ name: "create-index",
176
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: 1 } },
177
+ });
178
+
179
+ expect(getResponseContent(response.content)).toEqual(
180
+ `Created the index "prop1_1" on collection "coll1" in database "${integration.randomDbName()}"`
181
+ );
182
+
183
+ await validateIndex("coll1", [{ name: "prop1_1", key: { prop1: 1 } }]);
184
+ });
185
+
186
+ const testCases: { name: string; direction: IndexDirection }[] = [
187
+ { name: "descending", direction: -1 },
188
+ { name: "ascending", direction: 1 },
189
+ { name: "hashed", direction: "hashed" },
190
+ { name: "text", direction: "text" },
191
+ { name: "geoHaystack", direction: "2dsphere" },
192
+ { name: "geo2d", direction: "2d" },
193
+ ];
194
+
195
+ for (const { name, direction } of testCases) {
196
+ it(`creates ${name} index`, async () => {
197
+ await integration.connectMcpClient();
198
+ const response = await integration.mcpClient().callTool({
199
+ name: "create-index",
200
+ arguments: { database: integration.randomDbName(), collection: "coll1", keys: { prop1: direction } },
201
+ });
202
+
203
+ expect(getResponseContent(response.content)).toEqual(
204
+ `Created the index "prop1_${direction}" on collection "coll1" in database "${integration.randomDbName()}"`
205
+ );
206
+
207
+ let expectedKey: object = { prop1: direction };
208
+ if (direction === "text") {
209
+ expectedKey = {
210
+ _fts: "text",
211
+ _ftsx: 1,
212
+ };
213
+ }
214
+ await validateIndex("coll1", [{ name: `prop1_${direction}`, key: expectedKey }]);
215
+ });
216
+ }
217
+
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
+ });
248
+ });
249
+ });
@@ -0,0 +1,141 @@
1
+ import {
2
+ getResponseContent,
3
+ validateParameters,
4
+ dbOperationParameters,
5
+ setupIntegrationTest,
6
+ } 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
+
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
+ });
54
+
55
+ const validateDocuments = async (collection: string, expectedDocuments: object[]) => {
56
+ const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
57
+ expect(collections.find((c) => c.name === collection)).toBeDefined();
58
+
59
+ const docs = await integration
60
+ .mongoClient()
61
+ .db(integration.randomDbName())
62
+ .collection(collection)
63
+ .find()
64
+ .toArray();
65
+
66
+ expect(docs).toHaveLength(expectedDocuments.length);
67
+ for (const expectedDocument of expectedDocuments) {
68
+ expect(docs).toContainEqual(expect.objectContaining(expectedDocument));
69
+ }
70
+ };
71
+
72
+ it("creates the namespace if necessary", async () => {
73
+ await integration.connectMcpClient();
74
+ const response = await integration.mcpClient().callTool({
75
+ name: "insert-many",
76
+ arguments: {
77
+ database: integration.randomDbName(),
78
+ collection: "coll1",
79
+ documents: [{ prop1: "value1" }],
80
+ },
81
+ });
82
+
83
+ const content = getResponseContent(response.content);
84
+ expect(content).toContain('Inserted `1` document(s) into collection "coll1"');
85
+
86
+ await validateDocuments("coll1", [{ prop1: "value1" }]);
87
+ });
88
+
89
+ it("returns an error when inserting duplicates", async () => {
90
+ const { insertedIds } = await integration
91
+ .mongoClient()
92
+ .db(integration.randomDbName())
93
+ .collection("coll1")
94
+ .insertMany([{ prop1: "value1" }]);
95
+
96
+ await integration.connectMcpClient();
97
+ const response = await integration.mcpClient().callTool({
98
+ name: "insert-many",
99
+ arguments: {
100
+ database: integration.randomDbName(),
101
+ collection: "coll1",
102
+ documents: [{ prop1: "value1", _id: insertedIds[0] }],
103
+ },
104
+ });
105
+
106
+ const content = getResponseContent(response.content);
107
+ expect(content).toContain("Error running insert-many");
108
+ expect(content).toContain("duplicate key error");
109
+ expect(content).toContain(insertedIds[0].toString());
110
+ });
111
+
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
+ });
140
+ });
141
+ });
@@ -0,0 +1,191 @@
1
+ import {
2
+ getResponseContent,
3
+ validateParameters,
4
+ dbOperationParameters,
5
+ setupIntegrationTest,
6
+ } 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
+
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,
21
+ {
22
+ name: "filter",
23
+ type: "object",
24
+ description:
25
+ "The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()",
26
+ required: false,
27
+ },
28
+ ]);
29
+ });
30
+
31
+ describe("with invalid arguments", () => {
32
+ const args = [
33
+ {},
34
+ { collection: "bar", database: 123, filter: {} },
35
+ { collection: [], database: "test", filter: {} },
36
+ { collection: "bar", database: "test", filter: "my-document" },
37
+ { 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
+ }
53
+ });
54
+
55
+ it("doesn't create the collection if it doesn't exist", async () => {
56
+ await integration.connectMcpClient();
57
+ const response = await integration.mcpClient().callTool({
58
+ name: "delete-many",
59
+ arguments: {
60
+ database: integration.randomDbName(),
61
+ collection: "coll1",
62
+ filter: {},
63
+ },
64
+ });
65
+
66
+ const content = getResponseContent(response.content);
67
+ expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
68
+
69
+ const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
70
+ expect(collections).toHaveLength(0);
71
+ });
72
+
73
+ const insertDocuments = async () => {
74
+ await integration
75
+ .mongoClient()
76
+ .db(integration.randomDbName())
77
+ .collection("coll1")
78
+ .insertMany([
79
+ { age: 10, name: "Peter" },
80
+ { age: 20, name: "John" },
81
+ { age: 30, name: "Mary" },
82
+ { age: 40, name: "Lucy" },
83
+ ]);
84
+ };
85
+
86
+ const validateDocuments = async (expected: object[]) => {
87
+ const documents = await integration
88
+ .mongoClient()
89
+ .db(integration.randomDbName())
90
+ .collection("coll1")
91
+ .find()
92
+ .toArray();
93
+
94
+ expect(documents).toHaveLength(expected.length);
95
+ for (const expectedDocument of expected) {
96
+ expect(documents).toContainEqual(expect.objectContaining(expectedDocument));
97
+ }
98
+ };
99
+
100
+ it("deletes documents matching the filter", async () => {
101
+ await insertDocuments();
102
+
103
+ await integration.connectMcpClient();
104
+ const response = await integration.mcpClient().callTool({
105
+ name: "delete-many",
106
+ arguments: {
107
+ database: integration.randomDbName(),
108
+ collection: "coll1",
109
+ filter: { age: { $gt: 20 } },
110
+ },
111
+ });
112
+ const content = getResponseContent(response.content);
113
+ expect(content).toContain('Deleted `2` document(s) from collection "coll1"');
114
+
115
+ await validateDocuments([
116
+ { age: 10, name: "Peter" },
117
+ { age: 20, name: "John" },
118
+ ]);
119
+ });
120
+
121
+ it("when filter doesn't match, deletes nothing", async () => {
122
+ await insertDocuments();
123
+ await integration.connectMcpClient();
124
+ const response = await integration.mcpClient().callTool({
125
+ name: "delete-many",
126
+ arguments: {
127
+ database: integration.randomDbName(),
128
+ collection: "coll1",
129
+ filter: { age: { $gt: 100 } },
130
+ },
131
+ });
132
+
133
+ const content = getResponseContent(response.content);
134
+ expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
135
+
136
+ await validateDocuments([
137
+ { age: 10, name: "Peter" },
138
+ { age: 20, name: "John" },
139
+ { age: 30, name: "Mary" },
140
+ { age: 40, name: "Lucy" },
141
+ ]);
142
+ });
143
+
144
+ it("with empty filter, deletes all documents", async () => {
145
+ await insertDocuments();
146
+ await integration.connectMcpClient();
147
+ const response = await integration.mcpClient().callTool({
148
+ name: "delete-many",
149
+ arguments: {
150
+ database: integration.randomDbName(),
151
+ collection: "coll1",
152
+ filter: {},
153
+ },
154
+ });
155
+
156
+ const content = getResponseContent(response.content);
157
+ expect(content).toContain('Deleted `4` document(s) from collection "coll1"');
158
+
159
+ await validateDocuments([]);
160
+ });
161
+
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
+ });
190
+ });
191
+ });