mongodb-mcp-server 0.0.4 → 0.0.6

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 (185) hide show
  1. package/.github/CODEOWNERS +3 -0
  2. package/.github/dependabot.yml +10 -0
  3. package/.github/workflows/code_health.yaml +53 -22
  4. package/.github/workflows/code_health_fork.yaml +106 -0
  5. package/.github/workflows/codeql.yml +34 -0
  6. package/.github/workflows/lint.yml +37 -0
  7. package/.github/workflows/prepare_release.yaml +6 -4
  8. package/.github/workflows/publish.yaml +6 -3
  9. package/.prettierrc.json +1 -1
  10. package/README.md +18 -0
  11. package/dist/common/atlas/apiClient.js +28 -4
  12. package/dist/common/atlas/apiClient.js.map +1 -1
  13. package/dist/config.js +4 -7
  14. package/dist/config.js.map +1 -1
  15. package/dist/errors.js +1 -1
  16. package/dist/errors.js.map +1 -1
  17. package/dist/index.js +12 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/logger.js +72 -28
  20. package/dist/logger.js.map +1 -1
  21. package/dist/packageInfo.js +6 -0
  22. package/dist/packageInfo.js.map +1 -0
  23. package/dist/server.js +114 -10
  24. package/dist/server.js.map +1 -1
  25. package/dist/session.js +66 -16
  26. package/dist/session.js.map +1 -1
  27. package/dist/telemetry/constants.js +15 -0
  28. package/dist/telemetry/constants.js.map +1 -0
  29. package/dist/telemetry/eventCache.js +53 -0
  30. package/dist/telemetry/eventCache.js.map +1 -0
  31. package/dist/telemetry/telemetry.js +97 -0
  32. package/dist/telemetry/telemetry.js.map +1 -0
  33. package/dist/telemetry/types.js +2 -0
  34. package/dist/telemetry/types.js.map +1 -0
  35. package/dist/tools/atlas/atlasTool.js +8 -3
  36. package/dist/tools/atlas/atlasTool.js.map +1 -1
  37. package/dist/tools/atlas/{createAccessList.js → create/createAccessList.js} +1 -2
  38. package/dist/tools/atlas/create/createAccessList.js.map +1 -0
  39. package/dist/tools/atlas/{createDBUser.js → create/createDBUser.js} +1 -2
  40. package/dist/tools/atlas/create/createDBUser.js.map +1 -0
  41. package/dist/tools/atlas/{createFreeCluster.js → create/createFreeCluster.js} +5 -3
  42. package/dist/tools/atlas/create/createFreeCluster.js.map +1 -0
  43. package/dist/tools/atlas/{createProject.js → create/createProject.js} +1 -2
  44. package/dist/tools/atlas/create/createProject.js.map +1 -0
  45. package/dist/tools/atlas/metadata/connectCluster.js +97 -0
  46. package/dist/tools/atlas/metadata/connectCluster.js.map +1 -0
  47. package/dist/tools/atlas/{inspectAccessList.js → read/inspectAccessList.js} +1 -2
  48. package/dist/tools/atlas/read/inspectAccessList.js.map +1 -0
  49. package/dist/tools/atlas/{inspectCluster.js → read/inspectCluster.js} +1 -2
  50. package/dist/tools/atlas/read/inspectCluster.js.map +1 -0
  51. package/dist/tools/atlas/{listClusters.js → read/listClusters.js} +1 -2
  52. package/dist/tools/atlas/read/listClusters.js.map +1 -0
  53. package/dist/tools/atlas/{listDBUsers.js → read/listDBUsers.js} +1 -2
  54. package/dist/tools/atlas/read/listDBUsers.js.map +1 -0
  55. package/dist/tools/atlas/{listOrgs.js → read/listOrgs.js} +1 -2
  56. package/dist/tools/atlas/read/listOrgs.js.map +1 -0
  57. package/dist/tools/atlas/{listProjects.js → read/listProjects.js} +11 -5
  58. package/dist/tools/atlas/read/listProjects.js.map +1 -0
  59. package/dist/tools/atlas/tools.js +12 -10
  60. package/dist/tools/atlas/tools.js.map +1 -1
  61. package/dist/tools/mongodb/create/insertMany.js +1 -1
  62. package/dist/tools/mongodb/create/insertMany.js.map +1 -1
  63. package/dist/tools/mongodb/delete/deleteMany.js +1 -2
  64. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
  65. package/dist/tools/mongodb/metadata/collectionSchema.js +15 -13
  66. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
  67. package/dist/tools/mongodb/metadata/collectionStorageSize.js +32 -3
  68. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
  69. package/dist/tools/mongodb/metadata/connect.js +59 -72
  70. package/dist/tools/mongodb/metadata/connect.js.map +1 -1
  71. package/dist/tools/mongodb/metadata/dbStats.js +6 -1
  72. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
  73. package/dist/tools/mongodb/metadata/explain.js +14 -7
  74. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  75. package/dist/tools/mongodb/metadata/logs.js +45 -0
  76. package/dist/tools/mongodb/metadata/logs.js.map +1 -0
  77. package/dist/tools/mongodb/mongodbTool.js +42 -36
  78. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  79. package/dist/tools/mongodb/read/aggregate.js +4 -4
  80. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  81. package/dist/tools/mongodb/read/collectionIndexes.js +24 -5
  82. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
  83. package/dist/tools/mongodb/read/count.js +1 -2
  84. package/dist/tools/mongodb/read/count.js.map +1 -1
  85. package/dist/tools/mongodb/read/find.js +5 -6
  86. package/dist/tools/mongodb/read/find.js.map +1 -1
  87. package/dist/tools/mongodb/tools.js +6 -2
  88. package/dist/tools/mongodb/tools.js.map +1 -1
  89. package/dist/tools/mongodb/update/renameCollection.js +28 -4
  90. package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
  91. package/dist/tools/mongodb/update/updateMany.js +7 -11
  92. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  93. package/dist/tools/tool.js +68 -15
  94. package/dist/tools/tool.js.map +1 -1
  95. package/eslint.config.js +29 -10
  96. package/global.d.ts +1 -0
  97. package/package.json +7 -3
  98. package/scripts/apply.ts +9 -7
  99. package/scripts/filter.ts +3 -2
  100. package/src/common/atlas/apiClient.ts +44 -11
  101. package/src/config.ts +16 -17
  102. package/src/errors.ts +1 -1
  103. package/src/index.ts +12 -8
  104. package/src/logger.ts +92 -29
  105. package/src/packageInfo.ts +6 -0
  106. package/src/server.ts +160 -11
  107. package/src/session.ts +102 -22
  108. package/src/telemetry/constants.ts +15 -0
  109. package/src/telemetry/eventCache.ts +62 -0
  110. package/src/telemetry/telemetry.ts +125 -0
  111. package/src/telemetry/types.ts +77 -0
  112. package/src/tools/atlas/atlasTool.ts +7 -5
  113. package/src/tools/atlas/{createAccessList.ts → create/createAccessList.ts} +2 -4
  114. package/src/tools/atlas/{createDBUser.ts → create/createDBUser.ts} +3 -5
  115. package/src/tools/atlas/{createFreeCluster.ts → create/createFreeCluster.ts} +7 -6
  116. package/src/tools/atlas/{createProject.ts → create/createProject.ts} +3 -4
  117. package/src/tools/atlas/metadata/connectCluster.ts +114 -0
  118. package/src/tools/atlas/{inspectAccessList.ts → read/inspectAccessList.ts} +2 -4
  119. package/src/tools/atlas/{inspectCluster.ts → read/inspectCluster.ts} +3 -5
  120. package/src/tools/atlas/{listClusters.ts → read/listClusters.ts} +3 -5
  121. package/src/tools/atlas/{listDBUsers.ts → read/listDBUsers.ts} +3 -5
  122. package/src/tools/atlas/{listOrgs.ts → read/listOrgs.ts} +2 -4
  123. package/src/tools/atlas/{listProjects.ts → read/listProjects.ts} +15 -7
  124. package/src/tools/atlas/tools.ts +12 -10
  125. package/src/tools/mongodb/create/insertMany.ts +1 -1
  126. package/src/tools/mongodb/delete/deleteMany.ts +1 -2
  127. package/src/tools/mongodb/metadata/collectionSchema.ts +16 -14
  128. package/src/tools/mongodb/metadata/collectionStorageSize.ts +41 -3
  129. package/src/tools/mongodb/metadata/connect.ts +78 -76
  130. package/src/tools/mongodb/metadata/dbStats.ts +6 -1
  131. package/src/tools/mongodb/metadata/explain.ts +20 -7
  132. package/src/tools/mongodb/metadata/logs.ts +55 -0
  133. package/src/tools/mongodb/mongodbTool.ts +47 -40
  134. package/src/tools/mongodb/read/aggregate.ts +4 -4
  135. package/src/tools/mongodb/read/collectionIndexes.ts +29 -5
  136. package/src/tools/mongodb/read/count.ts +1 -2
  137. package/src/tools/mongodb/read/find.ts +5 -6
  138. package/src/tools/mongodb/tools.ts +6 -2
  139. package/src/tools/mongodb/update/renameCollection.ts +33 -4
  140. package/src/tools/mongodb/update/updateMany.ts +7 -11
  141. package/src/tools/tool.ts +89 -26
  142. package/tests/integration/helpers.ts +94 -107
  143. package/tests/integration/inMemoryTransport.ts +3 -2
  144. package/tests/integration/server.test.ts +75 -23
  145. package/tests/integration/tools/atlas/accessLists.test.ts +13 -15
  146. package/tests/integration/tools/atlas/atlasHelpers.ts +10 -13
  147. package/tests/integration/tools/atlas/clusters.test.ts +79 -16
  148. package/tests/integration/tools/atlas/dbUsers.test.ts +9 -9
  149. package/tests/integration/tools/atlas/orgs.test.ts +4 -4
  150. package/tests/integration/tools/atlas/projects.test.ts +10 -12
  151. package/tests/integration/tools/mongodb/create/createCollection.test.ts +19 -62
  152. package/tests/integration/tools/mongodb/create/createIndex.test.ts +41 -87
  153. package/tests/integration/tools/mongodb/create/insertMany.test.ts +35 -78
  154. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +25 -62
  155. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +22 -71
  156. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +29 -63
  157. package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +154 -0
  158. package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +86 -0
  159. package/tests/integration/tools/mongodb/metadata/connect.test.ts +88 -93
  160. package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +104 -0
  161. package/tests/integration/tools/mongodb/metadata/explain.test.ts +171 -0
  162. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +28 -56
  163. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +32 -26
  164. package/tests/integration/tools/mongodb/metadata/logs.test.ts +83 -0
  165. package/tests/integration/tools/mongodb/mongodbHelpers.ts +176 -0
  166. package/tests/integration/tools/mongodb/read/aggregate.test.ts +99 -0
  167. package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +99 -0
  168. package/tests/integration/tools/mongodb/read/count.test.ts +31 -79
  169. package/tests/integration/tools/mongodb/read/find.test.ts +182 -0
  170. package/tests/integration/tools/mongodb/update/renameCollection.test.ts +194 -0
  171. package/tests/integration/tools/mongodb/update/updateMany.test.ts +238 -0
  172. package/tests/unit/telemetry.test.ts +200 -0
  173. package/tsconfig.jest.json +2 -1
  174. package/tsconfig.json +1 -1
  175. package/tsconfig.lint.json +8 -0
  176. package/dist/tools/atlas/createAccessList.js.map +0 -1
  177. package/dist/tools/atlas/createDBUser.js.map +0 -1
  178. package/dist/tools/atlas/createFreeCluster.js.map +0 -1
  179. package/dist/tools/atlas/createProject.js.map +0 -1
  180. package/dist/tools/atlas/inspectAccessList.js.map +0 -1
  181. package/dist/tools/atlas/inspectCluster.js.map +0 -1
  182. package/dist/tools/atlas/listClusters.js.map +0 -1
  183. package/dist/tools/atlas/listDBUsers.js.map +0 -1
  184. package/dist/tools/atlas/listOrgs.js.map +0 -1
  185. package/dist/tools/atlas/listProjects.js.map +0 -1
@@ -0,0 +1,154 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
3
+ import {
4
+ getResponseElements,
5
+ getResponseContent,
6
+ databaseCollectionParameters,
7
+ validateToolMetadata,
8
+ validateThrowsForInvalidArguments,
9
+ databaseCollectionInvalidArgs,
10
+ } from "../../../helpers.js";
11
+ import { Document } from "bson";
12
+ import { OptionalId } from "mongodb";
13
+ import { SimplifiedSchema } from "mongodb-schema";
14
+
15
+ describeWithMongoDB("collectionSchema tool", (integration) => {
16
+ validateToolMetadata(
17
+ integration,
18
+ "collection-schema",
19
+ "Describe the schema for a collection",
20
+ databaseCollectionParameters
21
+ );
22
+
23
+ validateThrowsForInvalidArguments(integration, "collection-schema", databaseCollectionInvalidArgs);
24
+
25
+ describe("with non-existent database", () => {
26
+ it("returns empty schema", async () => {
27
+ await integration.connectMcpClient();
28
+ const response = await integration.mcpClient().callTool({
29
+ name: "collection-schema",
30
+ arguments: { database: "non-existent", collection: "foo" },
31
+ });
32
+ const content = getResponseContent(response.content);
33
+ expect(content).toEqual(
34
+ `Could not deduce the schema for "non-existent.foo". This may be because it doesn't exist or is empty.`
35
+ );
36
+ });
37
+ });
38
+
39
+ describe("with existing database", () => {
40
+ const testCases: Array<{
41
+ insertionData: OptionalId<Document>[];
42
+ name: string;
43
+ expectedSchema: SimplifiedSchema;
44
+ }> = [
45
+ {
46
+ name: "homogenous schema",
47
+ insertionData: [
48
+ { name: "Alice", age: 30 },
49
+ { name: "Bob", age: 25 },
50
+ ],
51
+ expectedSchema: {
52
+ _id: {
53
+ types: [{ bsonType: "ObjectId" }],
54
+ },
55
+ name: {
56
+ types: [{ bsonType: "String" }],
57
+ },
58
+ age: {
59
+ //@ts-expect-error This is a workaround
60
+ types: [{ bsonType: "Number" }],
61
+ },
62
+ },
63
+ },
64
+ {
65
+ name: "heterogenous schema",
66
+ insertionData: [
67
+ { name: "Alice", age: 30 },
68
+ { name: "Bob", age: "25", country: "UK" },
69
+ { name: "Charlie", country: "USA" },
70
+ { name: "Mims", age: 25, country: false },
71
+ ],
72
+ expectedSchema: {
73
+ _id: {
74
+ types: [{ bsonType: "ObjectId" }],
75
+ },
76
+ name: {
77
+ types: [{ bsonType: "String" }],
78
+ },
79
+ age: {
80
+ // @ts-expect-error This is a workaround
81
+ types: [{ bsonType: "Number" }, { bsonType: "String" }],
82
+ },
83
+ country: {
84
+ types: [{ bsonType: "String" }, { bsonType: "Boolean" }],
85
+ },
86
+ },
87
+ },
88
+ {
89
+ name: "schema with nested documents",
90
+ insertionData: [
91
+ { name: "Alice", address: { city: "New York", zip: "10001" }, ageRange: [18, 30] },
92
+ { name: "Bob", address: { city: "Los Angeles" }, ageRange: "25-30" },
93
+ { name: "Charlie", address: { city: "Chicago", zip: "60601" }, ageRange: [20, 35] },
94
+ ],
95
+ expectedSchema: {
96
+ _id: {
97
+ types: [{ bsonType: "ObjectId" }],
98
+ },
99
+ name: {
100
+ types: [{ bsonType: "String" }],
101
+ },
102
+ address: {
103
+ types: [
104
+ {
105
+ bsonType: "Document",
106
+ fields: {
107
+ city: { types: [{ bsonType: "String" }] },
108
+ zip: { types: [{ bsonType: "String" }] },
109
+ },
110
+ },
111
+ ],
112
+ },
113
+ ageRange: {
114
+ // @ts-expect-error This is a workaround
115
+ types: [{ bsonType: "Array", types: [{ bsonType: "Number" }] }, { bsonType: "String" }],
116
+ },
117
+ },
118
+ },
119
+ ];
120
+
121
+ for (const testCase of testCases) {
122
+ it(`returns ${testCase.name}`, async () => {
123
+ const mongoClient = integration.mongoClient();
124
+ await mongoClient.db(integration.randomDbName()).collection("foo").insertMany(testCase.insertionData);
125
+
126
+ await integration.connectMcpClient();
127
+ const response = await integration.mcpClient().callTool({
128
+ name: "collection-schema",
129
+ arguments: { database: integration.randomDbName(), collection: "foo" },
130
+ });
131
+ const items = getResponseElements(response.content);
132
+ expect(items).toHaveLength(2);
133
+
134
+ // Expect to find _id, name, age
135
+ expect(items[0].text).toEqual(
136
+ `Found ${Object.entries(testCase.expectedSchema).length} fields in the schema for "${integration.randomDbName()}.foo"`
137
+ );
138
+
139
+ const schema = JSON.parse(items[1].text) as SimplifiedSchema;
140
+ expect(schema).toEqual(testCase.expectedSchema);
141
+ });
142
+ }
143
+ });
144
+
145
+ validateAutoConnectBehavior(integration, "collection-schema", () => {
146
+ return {
147
+ args: {
148
+ database: integration.randomDbName(),
149
+ collection: "new-collection",
150
+ },
151
+ expectedResponse: `Could not deduce the schema for "${integration.randomDbName()}.new-collection". This may be because it doesn't exist or is empty.`,
152
+ };
153
+ });
154
+ });
@@ -0,0 +1,86 @@
1
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2
+
3
+ import {
4
+ getResponseContent,
5
+ databaseCollectionParameters,
6
+ databaseCollectionInvalidArgs,
7
+ validateToolMetadata,
8
+ validateThrowsForInvalidArguments,
9
+ expectDefined,
10
+ } from "../../../helpers.js";
11
+ import * as crypto from "crypto";
12
+
13
+ describeWithMongoDB("collectionStorageSize tool", (integration) => {
14
+ validateToolMetadata(
15
+ integration,
16
+ "collection-storage-size",
17
+ "Gets the size of the collection",
18
+ databaseCollectionParameters
19
+ );
20
+
21
+ validateThrowsForInvalidArguments(integration, "collection-storage-size", databaseCollectionInvalidArgs);
22
+
23
+ describe("with non-existent database", () => {
24
+ it("returns an error", async () => {
25
+ await integration.connectMcpClient();
26
+ const response = await integration.mcpClient().callTool({
27
+ name: "collection-storage-size",
28
+ arguments: { database: integration.randomDbName(), collection: "foo" },
29
+ });
30
+ const content = getResponseContent(response.content);
31
+ expect(content).toEqual(
32
+ `The size of "${integration.randomDbName()}.foo" cannot be determined because the collection does not exist.`
33
+ );
34
+ });
35
+ });
36
+
37
+ describe("with existing database", () => {
38
+ const testCases = [
39
+ {
40
+ expectedScale: "bytes",
41
+ bytesToInsert: 1,
42
+ },
43
+ {
44
+ expectedScale: "KB",
45
+ bytesToInsert: 1024,
46
+ },
47
+ {
48
+ expectedScale: "MB",
49
+ bytesToInsert: 1024 * 1024,
50
+ },
51
+ ];
52
+ for (const test of testCases) {
53
+ it(`returns the size of the collection in ${test.expectedScale}`, async () => {
54
+ await integration
55
+ .mongoClient()
56
+ .db(integration.randomDbName())
57
+ .collection("foo")
58
+ .insertOne({ data: crypto.randomBytes(test.bytesToInsert) });
59
+
60
+ await integration.connectMcpClient();
61
+ const response = await integration.mcpClient().callTool({
62
+ name: "collection-storage-size",
63
+ arguments: { database: integration.randomDbName(), collection: "foo" },
64
+ });
65
+ const content = getResponseContent(response.content);
66
+ expect(content).toContain(`The size of "${integration.randomDbName()}.foo" is`);
67
+ const size = /is `(\d+\.\d+) ([a-zA-Z]*)`/.exec(content);
68
+
69
+ expectDefined(size?.[1]);
70
+ expectDefined(size?.[2]);
71
+ expect(parseFloat(size?.[1] || "")).toBeGreaterThan(0);
72
+ expect(size?.[2]).toBe(test.expectedScale);
73
+ });
74
+ }
75
+ });
76
+
77
+ validateAutoConnectBehavior(integration, "collection-storage-size", () => {
78
+ return {
79
+ args: {
80
+ database: integration.randomDbName(),
81
+ collection: "foo",
82
+ },
83
+ expectedResponse: `The size of "${integration.randomDbName()}.foo" cannot be determined because the collection does not exist.`,
84
+ };
85
+ });
86
+ });
@@ -1,137 +1,132 @@
1
- import { getResponseContent, validateParameters, setupIntegrationTest } from "../../../helpers.js";
1
+ import { describeWithMongoDB } from "../mongodbHelpers.js";
2
+ import { getResponseContent, validateThrowsForInvalidArguments, validateToolMetadata } from "../../../helpers.js";
3
+ import { config } from "../../../../../src/config.js";
2
4
 
3
- import config from "../../../../../src/config.js";
4
-
5
- describe("Connect tool", () => {
6
- const integration = setupIntegrationTest();
7
-
8
- it("should have correct metadata", async () => {
9
- const { tools } = await integration.mcpClient().listTools();
10
- const connectTool = tools.find((tool) => tool.name === "connect")!;
11
- expect(connectTool).toBeDefined();
12
- expect(connectTool.description).toBe("Connect to a MongoDB instance");
5
+ // These tests are temporarily skipped because the connect tool is disabled for the initial release.
6
+ // TODO: https://github.com/mongodb-js/mongodb-mcp-server/issues/141 - reenable when the connect tool is reenabled
7
+ describeWithMongoDB(
8
+ "switchConnection tool",
9
+ (integration) => {
10
+ beforeEach(() => {
11
+ integration.mcpServer().userConfig.connectionString = integration.connectionString();
12
+ });
13
13
 
14
- validateParameters(connectTool, [
15
- {
16
- name: "options",
17
- description:
18
- "Options for connecting to MongoDB. If not provided, the connection string from the config://connection-string resource will be used. If the user hasn't specified Atlas cluster name or a connection string explicitly and the `config://connection-string` resource is present, always invoke this with no arguments.",
19
- type: "array",
20
- required: false,
21
- },
22
- ]);
23
- });
14
+ validateToolMetadata(
15
+ integration,
16
+ "switch-connection",
17
+ "Switch to a different MongoDB connection. If the user has configured a connection string or has previously called the connect tool, a connection is already established and there's no need to call this tool unless the user has explicitly requested to switch to a new instance.",
18
+ [
19
+ {
20
+ name: "connectionString",
21
+ description: "MongoDB connection string to switch to (in the mongodb:// or mongodb+srv:// format)",
22
+ type: "string",
23
+ required: false,
24
+ },
25
+ ]
26
+ );
24
27
 
25
- describe("with default config", () => {
26
- describe("without connection string", () => {
27
- it("prompts for connection string", async () => {
28
- const response = await integration.mcpClient().callTool({ name: "connect", arguments: {} });
29
- const content = getResponseContent(response.content);
30
- expect(content).toContain("No connection details provided");
31
- });
32
- });
28
+ validateThrowsForInvalidArguments(integration, "switch-connection", [{ connectionString: 123 }]);
33
29
 
34
- describe("with connection string", () => {
30
+ describe("without arguments", () => {
35
31
  it("connects to the database", async () => {
36
- const response = await integration.mcpClient().callTool({
37
- name: "connect",
38
- arguments: {
39
- options: [
40
- {
41
- connectionString: integration.connectionString(),
42
- },
43
- ],
44
- },
45
- });
32
+ const response = await integration.mcpClient().callTool({ name: "switch-connection" });
46
33
  const content = getResponseContent(response.content);
47
34
  expect(content).toContain("Successfully connected");
48
- expect(content).toContain(integration.connectionString());
49
- });
50
- });
51
-
52
- describe("with invalid connection string", () => {
53
- it("returns error message", async () => {
54
- const response = await integration.mcpClient().callTool({
55
- name: "connect",
56
- arguments: { options: [{ connectionString: "mongodb://localhost:12345" }] },
57
- });
58
- const content = getResponseContent(response.content);
59
- expect(content).toContain("Error running connect");
60
-
61
- // Should not suggest using the config connection string (because we don't have one)
62
- expect(content).not.toContain("Your config lists a different connection string");
63
35
  });
64
36
  });
65
- });
66
37
 
67
- describe("with connection string in config", () => {
68
- beforeEach(async () => {
69
- config.connectionString = integration.connectionString();
38
+ it("doesn't have the connect tool registered", async () => {
39
+ const { tools } = await integration.mcpClient().listTools();
40
+ const tool = tools.find((tool) => tool.name === "connect");
41
+ expect(tool).toBeUndefined();
70
42
  });
71
43
 
72
- it("uses the connection string from config", async () => {
73
- const response = await integration.mcpClient().callTool({ name: "connect", arguments: {} });
44
+ it("defaults to the connection string from config", async () => {
45
+ const response = await integration.mcpClient().callTool({ name: "switch-connection", arguments: {} });
74
46
  const content = getResponseContent(response.content);
75
47
  expect(content).toContain("Successfully connected");
76
- expect(content).toContain(integration.connectionString());
77
48
  });
78
49
 
79
- it("prefers connection string from arguments", async () => {
50
+ it("switches to the connection string from the arguments", async () => {
80
51
  const newConnectionString = `${integration.connectionString()}?appName=foo-bar`;
81
52
  const response = await integration.mcpClient().callTool({
82
- name: "connect",
53
+ name: "switch-connection",
83
54
  arguments: {
84
- options: [
85
- {
86
- connectionString: newConnectionString,
87
- },
88
- ],
55
+ connectionString: newConnectionString,
89
56
  },
90
57
  });
91
58
  const content = getResponseContent(response.content);
92
59
  expect(content).toContain("Successfully connected");
93
- expect(content).toContain(newConnectionString);
94
60
  });
95
61
 
96
62
  describe("when the arugment connection string is invalid", () => {
97
- it("suggests the config connection string if set", async () => {
63
+ it("returns error message", async () => {
98
64
  const response = await integration.mcpClient().callTool({
99
- name: "connect",
65
+ name: "switch-connection",
100
66
  arguments: {
101
- options: [
102
- {
103
- connectionString: "mongodb://localhost:12345",
104
- },
105
- ],
67
+ connectionString: "mongodb://localhost:12345",
106
68
  },
107
69
  });
70
+
108
71
  const content = getResponseContent(response.content);
109
- expect(content).toContain("Failed to connect to MongoDB at 'mongodb://localhost:12345'");
110
- expect(content).toContain(
111
- `Your config lists a different connection string: '${config.connectionString}' - do you want to try connecting to it instead?`
112
- );
72
+
73
+ expect(content).toContain("Error running switch-connection");
113
74
  });
75
+ });
76
+ },
77
+ (mdbIntegration) => ({
78
+ ...config,
79
+ connectionString: mdbIntegration.connectionString(),
80
+ }),
81
+ describe.skip
82
+ );
83
+ describeWithMongoDB(
84
+ "Connect tool",
85
+ (integration) => {
86
+ validateToolMetadata(integration, "connect", "Connect to a MongoDB instance", [
87
+ {
88
+ name: "connectionString",
89
+ description: "MongoDB connection string (in the mongodb:// or mongodb+srv:// format)",
90
+ type: "string",
91
+ required: true,
92
+ },
93
+ ]);
94
+
95
+ validateThrowsForInvalidArguments(integration, "connect", [{}, { connectionString: 123 }]);
114
96
 
115
- it("returns error message if the config connection string matches the argument", async () => {
116
- config.connectionString = "mongodb://localhost:12345";
97
+ it("doesn't have the switch-connection tool registered", async () => {
98
+ const { tools } = await integration.mcpClient().listTools();
99
+ const tool = tools.find((tool) => tool.name === "switch-connection");
100
+ expect(tool).toBeUndefined();
101
+ });
102
+
103
+ describe("with connection string", () => {
104
+ it("connects to the database", async () => {
117
105
  const response = await integration.mcpClient().callTool({
118
106
  name: "connect",
119
107
  arguments: {
120
- options: [
121
- {
122
- connectionString: "mongodb://localhost:12345",
123
- },
124
- ],
108
+ connectionString: integration.connectionString(),
125
109
  },
126
110
  });
127
-
128
111
  const content = getResponseContent(response.content);
112
+ expect(content).toContain("Successfully connected");
113
+ });
114
+ });
129
115
 
130
- // Should be handled by default error handler and not suggest the config connection string
131
- // because it matches the argument connection string
116
+ describe("with invalid connection string", () => {
117
+ it("returns error message", async () => {
118
+ const response = await integration.mcpClient().callTool({
119
+ name: "connect",
120
+ arguments: { connectionString: "mongodb://localhost:12345" },
121
+ });
122
+ const content = getResponseContent(response.content);
132
123
  expect(content).toContain("Error running connect");
124
+
125
+ // Should not suggest using the config connection string (because we don't have one)
133
126
  expect(content).not.toContain("Your config lists a different connection string");
134
127
  });
135
128
  });
136
- });
137
- });
129
+ },
130
+ () => config,
131
+ describe.skip
132
+ );
@@ -0,0 +1,104 @@
1
+ import { ObjectId } from "bson";
2
+ import {
3
+ databaseParameters,
4
+ validateToolMetadata,
5
+ validateThrowsForInvalidArguments,
6
+ databaseInvalidArgs,
7
+ getResponseElements,
8
+ } from "../../../helpers.js";
9
+ import * as crypto from "crypto";
10
+ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
11
+
12
+ describeWithMongoDB("dbStats tool", (integration) => {
13
+ validateToolMetadata(
14
+ integration,
15
+ "db-stats",
16
+ "Returns statistics that reflect the use state of a single database",
17
+ databaseParameters
18
+ );
19
+
20
+ validateThrowsForInvalidArguments(integration, "db-stats", databaseInvalidArgs);
21
+
22
+ describe("with non-existent database", () => {
23
+ it("returns an error", async () => {
24
+ await integration.connectMcpClient();
25
+ const response = await integration.mcpClient().callTool({
26
+ name: "db-stats",
27
+ arguments: { database: integration.randomDbName() },
28
+ });
29
+ const elements = getResponseElements(response.content);
30
+ expect(elements).toHaveLength(2);
31
+ expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`);
32
+
33
+ const stats = JSON.parse(elements[1].text) as {
34
+ db: string;
35
+ collections: number;
36
+ storageSize: number;
37
+ };
38
+ expect(stats.db).toBe(integration.randomDbName());
39
+ expect(stats.collections).toBe(0);
40
+ expect(stats.storageSize).toBe(0);
41
+ });
42
+ });
43
+
44
+ describe("with existing database", () => {
45
+ const testCases = [
46
+ {
47
+ collections: {
48
+ foos: 3,
49
+ },
50
+ name: "single collection",
51
+ },
52
+ {
53
+ collections: {
54
+ foos: 2,
55
+ bars: 5,
56
+ },
57
+ name: "multiple collections",
58
+ },
59
+ ];
60
+ for (const test of testCases) {
61
+ it(`returns correct stats for ${test.name}`, async () => {
62
+ for (const [name, count] of Object.entries(test.collections)) {
63
+ const objects = Array(count)
64
+ .fill(0)
65
+ .map(() => {
66
+ return { data: crypto.randomBytes(1024), _id: new ObjectId() };
67
+ });
68
+ await integration.mongoClient().db(integration.randomDbName()).collection(name).insertMany(objects);
69
+ }
70
+
71
+ await integration.connectMcpClient();
72
+ const response = await integration.mcpClient().callTool({
73
+ name: "db-stats",
74
+ arguments: { database: integration.randomDbName() },
75
+ });
76
+ const elements = getResponseElements(response.content);
77
+ expect(elements).toHaveLength(2);
78
+ expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`);
79
+
80
+ const stats = JSON.parse(elements[1].text) as {
81
+ db: string;
82
+ collections: unknown;
83
+ storageSize: unknown;
84
+ objects: unknown;
85
+ };
86
+ expect(stats.db).toBe(integration.randomDbName());
87
+ expect(stats.collections).toBe(Object.entries(test.collections).length);
88
+ expect(stats.storageSize).toBeGreaterThan(1024);
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
90
+ expect(stats.objects).toBe(Object.values(test.collections).reduce((a, b) => a + b, 0));
91
+ });
92
+ }
93
+ });
94
+
95
+ validateAutoConnectBehavior(integration, "db-stats", () => {
96
+ return {
97
+ args: {
98
+ database: integration.randomDbName(),
99
+ collection: "foo",
100
+ },
101
+ expectedResponse: `Statistics for database ${integration.randomDbName()}`,
102
+ };
103
+ });
104
+ });