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,14 +1,10 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { InMemoryTransport } from "./inMemoryTransport.js";
3
3
  import { Server } from "../../src/server.js";
4
- import runner, { MongoCluster } from "mongodb-runner";
5
- import path from "path";
6
- import fs from "fs/promises";
7
- import { Session } from "../../src/session.js";
4
+ import { config, UserConfig } from "../../src/config.js";
5
+ import { McpError } from "@modelcontextprotocol/sdk/types.js";
8
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
- import { MongoClient, ObjectId } from "mongodb";
10
- import { toIncludeAllMembers } from "jest-extended";
11
- import config from "../../src/config.js";
7
+ import { Session } from "../../src/session.js";
12
8
 
13
9
  interface ParameterInfo {
14
10
  name: string;
@@ -22,21 +18,12 @@ type ToolInfo = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
22
18
  export interface IntegrationTest {
23
19
  mcpClient: () => Client;
24
20
  mcpServer: () => Server;
25
- mongoClient: () => MongoClient;
26
- connectionString: () => string;
27
- connectMcpClient: () => Promise<void>;
28
- randomDbName: () => string;
29
21
  }
30
22
 
31
- export function setupIntegrationTest(): IntegrationTest {
32
- let mongoCluster: runner.MongoCluster | undefined;
33
- let mongoClient: MongoClient | undefined;
34
-
23
+ export function setupIntegrationTest(userConfig: UserConfig = config): IntegrationTest {
35
24
  let mcpClient: Client | undefined;
36
25
  let mcpServer: Server | undefined;
37
26
 
38
- let randomDbName: string;
39
-
40
27
  beforeAll(async () => {
41
28
  const clientTransport = new InMemoryTransport();
42
29
  const serverTransport = new InMemoryTransport();
@@ -44,8 +31,8 @@ export function setupIntegrationTest(): IntegrationTest {
44
31
  await serverTransport.start();
45
32
  await clientTransport.start();
46
33
 
47
- clientTransport.output.pipeTo(serverTransport.input);
48
- serverTransport.output.pipeTo(clientTransport.input);
34
+ void clientTransport.output.pipeTo(serverTransport.input);
35
+ void serverTransport.output.pipeTo(clientTransport.input);
49
36
 
50
37
  mcpClient = new Client(
51
38
  {
@@ -57,19 +44,26 @@ export function setupIntegrationTest(): IntegrationTest {
57
44
  }
58
45
  );
59
46
 
47
+ const session = new Session({
48
+ apiBaseUrl: userConfig.apiBaseUrl,
49
+ apiClientId: userConfig.apiClientId,
50
+ apiClientSecret: userConfig.apiClientSecret,
51
+ });
52
+
60
53
  mcpServer = new Server({
54
+ session,
55
+ userConfig,
61
56
  mcpServer: new McpServer({
62
57
  name: "test-server",
63
58
  version: "1.2.3",
64
59
  }),
65
- session: new Session(),
66
60
  });
67
61
  await mcpServer.connect(serverTransport);
68
62
  await mcpClient.connect(clientTransport);
69
63
  });
70
64
 
71
- beforeEach(async () => {
72
- randomDbName = new ObjectId().toString();
65
+ beforeEach(() => {
66
+ config.telemetry = "disabled";
73
67
  });
74
68
 
75
69
  afterAll(async () => {
@@ -80,52 +74,6 @@ export function setupIntegrationTest(): IntegrationTest {
80
74
  mcpServer = undefined;
81
75
  });
82
76
 
83
- afterEach(async () => {
84
- await mcpServer?.session.close();
85
- config.connectionString = undefined;
86
- });
87
-
88
- beforeAll(async function () {
89
- // Downloading Windows executables in CI takes a long time because
90
- // they include debug symbols...
91
- const tmpDir = path.join(__dirname, "..", "tmp");
92
- await fs.mkdir(tmpDir, { recursive: true });
93
-
94
- // On Windows, we may have a situation where mongod.exe is not fully released by the OS
95
- // before we attempt to run it again, so we add a retry.
96
- let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs");
97
- for (let i = 0; i < 10; i++) {
98
- try {
99
- mongoCluster = await MongoCluster.start({
100
- tmpDir: dbsDir,
101
- logDir: path.join(tmpDir, "mongodb-runner", "logs"),
102
- topology: "standalone",
103
- });
104
-
105
- return;
106
- } catch (err) {
107
- if (i < 5) {
108
- // Just wait a little bit and retry
109
- console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`);
110
- await new Promise((resolve) => setTimeout(resolve, 1000));
111
- } else {
112
- // If we still fail after 5 seconds, try another db dir
113
- console.error(
114
- `Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}. Retrying with a new db dir.`
115
- );
116
- dbsDir = path.join(tmpDir, "mongodb-runner", `dbs${i - 5}`);
117
- }
118
- }
119
- }
120
-
121
- throw new Error("Failed to start cluster after 10 attempts");
122
- }, 120_000);
123
-
124
- afterAll(async function () {
125
- await mongoCluster?.close();
126
- mongoCluster = undefined;
127
- });
128
-
129
77
  const getMcpClient = () => {
130
78
  if (!mcpClient) {
131
79
  throw new Error("beforeEach() hook not ran yet");
@@ -142,41 +90,25 @@ export function setupIntegrationTest(): IntegrationTest {
142
90
  return mcpServer;
143
91
  };
144
92
 
145
- const getConnectionString = () => {
146
- if (!mongoCluster) {
147
- throw new Error("beforeAll() hook not ran yet");
148
- }
149
-
150
- return mongoCluster.connectionString;
151
- };
152
-
153
93
  return {
154
94
  mcpClient: getMcpClient,
155
95
  mcpServer: getMcpServer,
156
- mongoClient: () => {
157
- if (!mongoClient) {
158
- mongoClient = new MongoClient(getConnectionString());
159
- }
160
- return mongoClient;
161
- },
162
- connectionString: getConnectionString,
163
- connectMcpClient: async () => {
164
- await getMcpClient().callTool({
165
- name: "connect",
166
- arguments: { options: [{ connectionString: getConnectionString() }] },
167
- });
168
- },
169
- randomDbName: () => randomDbName,
170
96
  };
171
97
  }
172
98
 
173
- export function getResponseContent(content: unknown): string {
99
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
100
+ export function getResponseContent(content: unknown | { content: unknown }): string {
174
101
  return getResponseElements(content)
175
102
  .map((item) => item.text)
176
103
  .join("\n");
177
104
  }
178
105
 
179
- export function getResponseElements(content: unknown): { type: string; text: string }[] {
106
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
107
+ export function getResponseElements(content: unknown | { content: unknown }): { type: string; text: string }[] {
108
+ if (typeof content === "object" && content !== null && "content" in content) {
109
+ content = (content as { content: unknown }).content;
110
+ }
111
+
180
112
  expect(Array.isArray(content)).toBe(true);
181
113
 
182
114
  const response = content as { type: string; text: string }[];
@@ -198,9 +130,9 @@ export async function connect(client: Client, connectionString: string): Promise
198
130
 
199
131
  export function getParameters(tool: ToolInfo): ParameterInfo[] {
200
132
  expect(tool.inputSchema.type).toBe("object");
201
- expect(tool.inputSchema.properties).toBeDefined();
133
+ expectDefined(tool.inputSchema.properties);
202
134
 
203
- return Object.entries(tool.inputSchema.properties!)
135
+ return Object.entries(tool.inputSchema.properties)
204
136
  .sort((a, b) => a[0].localeCompare(b[0]))
205
137
  .map(([key, value]) => {
206
138
  expect(value).toHaveProperty("type");
@@ -218,24 +150,69 @@ export function getParameters(tool: ToolInfo): ParameterInfo[] {
218
150
  });
219
151
  }
220
152
 
221
- export const dbOperationParameters: ParameterInfo[] = [
153
+ export const databaseParameters: ParameterInfo[] = [
222
154
  { name: "database", type: "string", description: "Database name", required: true },
155
+ ];
156
+
157
+ export const databaseCollectionParameters: ParameterInfo[] = [
158
+ ...databaseParameters,
223
159
  { name: "collection", type: "string", description: "Collection name", required: true },
224
160
  ];
225
161
 
226
- export function validateParameters(tool: ToolInfo, parameters: ParameterInfo[]): void {
227
- const toolParameters = getParameters(tool);
228
- expect(toolParameters).toHaveLength(parameters.length);
229
- expect(toolParameters).toIncludeAllMembers(parameters);
162
+ export const databaseCollectionInvalidArgs = [
163
+ {},
164
+ { database: "test" },
165
+ { collection: "foo" },
166
+ { database: 123, collection: "foo" },
167
+ { database: "test", collection: 123 },
168
+ { database: [], collection: "foo" },
169
+ { database: "test", collection: [] },
170
+ ];
171
+
172
+ export const databaseInvalidArgs = [{}, { database: 123 }, { database: [] }];
173
+
174
+ export function validateToolMetadata(
175
+ integration: IntegrationTest,
176
+ name: string,
177
+ description: string,
178
+ parameters: ParameterInfo[]
179
+ ): void {
180
+ it("should have correct metadata", async () => {
181
+ const { tools } = await integration.mcpClient().listTools();
182
+ const tool = tools.find((tool) => tool.name === name);
183
+ expectDefined(tool);
184
+ expect(tool.description).toBe(description);
185
+
186
+ const toolParameters = getParameters(tool);
187
+ expect(toolParameters).toHaveLength(parameters.length);
188
+ expect(toolParameters).toIncludeAllMembers(parameters);
189
+ });
230
190
  }
231
191
 
232
- export function describeAtlas(name: number | string | Function | jest.FunctionLike, fn: jest.EmptyFunction) {
233
- if (!process.env.MDB_MCP_API_CLIENT_ID?.length || !process.env.MDB_MCP_API_CLIENT_SECRET?.length) {
234
- return describe.skip("atlas", () => {
235
- describe(name, fn);
236
- });
237
- }
238
- return describe("atlas", () => {
239
- describe(name, fn);
192
+ export function validateThrowsForInvalidArguments(
193
+ integration: IntegrationTest,
194
+ name: string,
195
+ args: { [x: string]: unknown }[]
196
+ ): void {
197
+ describe("with invalid arguments", () => {
198
+ for (const arg of args) {
199
+ it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
200
+ try {
201
+ await integration.mcpClient().callTool({ name, arguments: arg });
202
+ throw new Error("Expected an error to be thrown");
203
+ } catch (error) {
204
+ expect((error as Error).message).not.toEqual("Expected an error to be thrown");
205
+ expect(error).toBeInstanceOf(McpError);
206
+ const mcpError = error as McpError;
207
+ expect(mcpError.code).toEqual(-32602);
208
+ expect(mcpError.message).toContain(`Invalid arguments for tool ${name}`);
209
+ }
210
+ });
211
+ }
240
212
  });
241
213
  }
214
+
215
+ /** Expects the argument being defined and asserts it */
216
+ export function expectDefined<T>(arg: T): asserts arg is Exclude<T, undefined> {
217
+ expect(arg).toBeDefined();
218
+ }
@@ -39,6 +39,7 @@ export class InMemoryTransport implements Transport {
39
39
  return Promise.resolve();
40
40
  }
41
41
 
42
+ // eslint-disable-next-line @typescript-eslint/require-await
42
43
  async close(): Promise<void> {
43
44
  this.outputController.close();
44
45
  this.onclose?.();
@@ -49,10 +50,10 @@ export class InMemoryTransport implements Transport {
49
50
  sessionId?: string | undefined;
50
51
 
51
52
  private static getPromise(): [Promise<void>, resolve: () => void] {
52
- let resolve: () => void;
53
+ let resolve: () => void = () => {};
53
54
  const promise = new Promise<void>((res) => {
54
55
  resolve = res;
55
56
  });
56
- return [promise, resolve!];
57
+ return [promise, resolve];
57
58
  }
58
59
  }
@@ -1,35 +1,61 @@
1
- import { setupIntegrationTest } from "./helpers";
1
+ import { expectDefined, setupIntegrationTest } from "./helpers.js";
2
+ import { config } from "../../src/config.js";
2
3
 
3
4
  describe("Server integration test", () => {
4
- const integration = setupIntegrationTest();
5
+ describe("without atlas", () => {
6
+ const integration = setupIntegrationTest({
7
+ ...config,
8
+ apiClientId: undefined,
9
+ apiClientSecret: undefined,
10
+ });
5
11
 
6
- describe("list capabilities", () => {
7
- it("should return positive number of tools", async () => {
12
+ it("should return positive number of tools and have no atlas tools", async () => {
8
13
  const tools = await integration.mcpClient().listTools();
9
- expect(tools).toBeDefined();
14
+ expectDefined(tools);
10
15
  expect(tools.tools.length).toBeGreaterThan(0);
16
+
17
+ const atlasTools = tools.tools.filter((tool) => tool.name.startsWith("atlas-"));
18
+ expect(atlasTools.length).toBeLessThanOrEqual(0);
11
19
  });
20
+ });
21
+ describe("with atlas", () => {
22
+ const integration = setupIntegrationTest({
23
+ ...config,
24
+ apiClientId: "test",
25
+ apiClientSecret: "test",
26
+ });
27
+
28
+ describe("list capabilities", () => {
29
+ it("should return positive number of tools and have some atlas tools", async () => {
30
+ const tools = await integration.mcpClient().listTools();
31
+ expectDefined(tools);
32
+ expect(tools.tools.length).toBeGreaterThan(0);
12
33
 
13
- it("should return no resources", async () => {
14
- await expect(() => integration.mcpClient().listResources()).rejects.toMatchObject({
15
- message: "MCP error -32601: Method not found",
34
+ const atlasTools = tools.tools.filter((tool) => tool.name.startsWith("atlas-"));
35
+ expect(atlasTools.length).toBeGreaterThan(0);
16
36
  });
17
- });
18
37
 
19
- it("should return no prompts", async () => {
20
- await expect(() => integration.mcpClient().listPrompts()).rejects.toMatchObject({
21
- message: "MCP error -32601: Method not found",
38
+ it("should return no resources", async () => {
39
+ await expect(() => integration.mcpClient().listResources()).rejects.toMatchObject({
40
+ message: "MCP error -32601: Method not found",
41
+ });
22
42
  });
23
- });
24
43
 
25
- it("should return capabilities", async () => {
26
- const capabilities = integration.mcpClient().getServerCapabilities();
27
- expect(capabilities).toBeDefined();
28
- expect(capabilities?.completions).toBeUndefined();
29
- expect(capabilities?.experimental).toBeUndefined();
30
- expect(capabilities?.tools).toBeDefined();
31
- expect(capabilities?.logging).toBeDefined();
32
- expect(capabilities?.prompts).toBeUndefined();
44
+ it("should return no prompts", async () => {
45
+ await expect(() => integration.mcpClient().listPrompts()).rejects.toMatchObject({
46
+ message: "MCP error -32601: Method not found",
47
+ });
48
+ });
49
+
50
+ it("should return capabilities", () => {
51
+ const capabilities = integration.mcpClient().getServerCapabilities();
52
+ expectDefined(capabilities);
53
+ expect(capabilities.completions).toBeUndefined();
54
+ expect(capabilities.experimental).toBeUndefined();
55
+ expectDefined(capabilities?.tools);
56
+ expectDefined(capabilities?.logging);
57
+ expect(capabilities?.prompts).toBeUndefined();
58
+ });
33
59
  });
34
60
  });
35
61
  });
@@ -1,6 +1,6 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
- import { Session } from "../../../../src/session.js";
3
- import { describeAtlas, withProject } from "./atlasHelpers.js";
2
+ import { describeWithAtlas, withProject } from "./atlasHelpers.js";
3
+ import { expectDefined } from "../../helpers.js";
4
4
 
5
5
  function generateRandomIp() {
6
6
  const randomIp: number[] = [192];
@@ -10,27 +10,25 @@ function generateRandomIp() {
10
10
  return randomIp.join(".");
11
11
  }
12
12
 
13
- describeAtlas("ip access lists", (integration) => {
13
+ describeWithAtlas("ip access lists", (integration) => {
14
14
  withProject(integration, ({ getProjectId }) => {
15
15
  const ips = [generateRandomIp(), generateRandomIp()];
16
16
  const cidrBlocks = [generateRandomIp() + "/16", generateRandomIp() + "/24"];
17
17
  const values = [...ips, ...cidrBlocks];
18
18
 
19
19
  beforeAll(async () => {
20
- const session: Session = integration.mcpServer().session;
21
- session.ensureAuthenticated();
22
- const ipInfo = await session.apiClient.getIpInfo();
20
+ const apiClient = integration.mcpServer().session.apiClient;
21
+ const ipInfo = await apiClient.getIpInfo();
23
22
  values.push(ipInfo.currentIpv4Address);
24
23
  });
25
24
 
26
25
  afterAll(async () => {
27
- const session: Session = integration.mcpServer().session;
28
- session.ensureAuthenticated();
26
+ const apiClient = integration.mcpServer().session.apiClient;
29
27
 
30
28
  const projectId = getProjectId();
31
29
 
32
30
  for (const value of values) {
33
- await session.apiClient.deleteProjectIpAccessList({
31
+ await apiClient.deleteProjectIpAccessList({
34
32
  params: {
35
33
  path: {
36
34
  groupId: projectId,
@@ -44,10 +42,10 @@ describeAtlas("ip access lists", (integration) => {
44
42
  describe("atlas-create-access-list", () => {
45
43
  it("should have correct metadata", async () => {
46
44
  const { tools } = await integration.mcpClient().listTools();
47
- const createAccessList = tools.find((tool) => tool.name === "atlas-create-access-list")!;
48
- expect(createAccessList).toBeDefined();
45
+ const createAccessList = tools.find((tool) => tool.name === "atlas-create-access-list");
46
+ expectDefined(createAccessList);
49
47
  expect(createAccessList.inputSchema.type).toBe("object");
50
- expect(createAccessList.inputSchema.properties).toBeDefined();
48
+ expectDefined(createAccessList.inputSchema.properties);
51
49
  expect(createAccessList.inputSchema.properties).toHaveProperty("projectId");
52
50
  expect(createAccessList.inputSchema.properties).toHaveProperty("ipAddresses");
53
51
  expect(createAccessList.inputSchema.properties).toHaveProperty("cidrBlocks");
@@ -76,10 +74,10 @@ describeAtlas("ip access lists", (integration) => {
76
74
  describe("atlas-inspect-access-list", () => {
77
75
  it("should have correct metadata", async () => {
78
76
  const { tools } = await integration.mcpClient().listTools();
79
- const inspectAccessList = tools.find((tool) => tool.name === "atlas-inspect-access-list")!;
80
- expect(inspectAccessList).toBeDefined();
77
+ const inspectAccessList = tools.find((tool) => tool.name === "atlas-inspect-access-list");
78
+ expectDefined(inspectAccessList);
81
79
  expect(inspectAccessList.inputSchema.type).toBe("object");
82
- expect(inspectAccessList.inputSchema.properties).toBeDefined();
80
+ expectDefined(inspectAccessList.inputSchema.properties);
83
81
  expect(inspectAccessList.inputSchema.properties).toHaveProperty("projectId");
84
82
  });
85
83
 
@@ -2,7 +2,6 @@ import { ObjectId } from "mongodb";
2
2
  import { Group } from "../../../../src/common/atlas/openapi.js";
3
3
  import { ApiClient } from "../../../../src/common/atlas/apiClient.js";
4
4
  import { setupIntegrationTest, IntegrationTest } from "../../helpers.js";
5
- import { Session } from "../../../../src/session.js";
6
5
 
7
6
  export type IntegrationTestFunction = (integration: IntegrationTest) => void;
8
7
 
@@ -10,7 +9,7 @@ export function sleep(ms: number) {
10
9
  return new Promise((resolve) => setTimeout(resolve, ms));
11
10
  }
12
11
 
13
- export function describeAtlas(name: number | string | Function | jest.FunctionLike, fn: IntegrationTestFunction) {
12
+ export function describeWithAtlas(name: string, fn: IntegrationTestFunction) {
14
13
  const testDefinition = () => {
15
14
  const integration = setupIntegrationTest();
16
15
  describe(name, () => {
@@ -35,19 +34,15 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
35
34
  let projectId: string = "";
36
35
 
37
36
  beforeAll(async () => {
38
- const session: Session = integration.mcpServer().session;
39
- session.ensureAuthenticated();
37
+ const apiClient = integration.mcpServer().session.apiClient;
40
38
 
41
- const apiClient = session.apiClient;
42
39
  const group = await createProject(apiClient);
43
40
  projectId = group.id || "";
44
41
  });
45
42
 
46
43
  afterAll(async () => {
47
- const session: Session = integration.mcpServer().session;
48
- session.ensureAuthenticated();
44
+ const apiClient = integration.mcpServer().session.apiClient;
49
45
 
50
- const apiClient = session.apiClient;
51
46
  await apiClient.deleteProject({
52
47
  params: {
53
48
  path: {
@@ -1,10 +1,9 @@
1
1
  import { Session } from "../../../../src/session.js";
2
- import { describeAtlas, withProject, sleep, randomId } from "./atlasHelpers.js";
2
+ import { expectDefined } from "../../helpers.js";
3
+ import { describeWithAtlas, withProject, sleep, randomId } from "./atlasHelpers.js";
3
4
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
5
 
5
6
  async function deleteAndWaitCluster(session: Session, projectId: string, clusterName: string) {
6
- session.ensureAuthenticated();
7
-
8
7
  await session.apiClient.deleteCluster({
9
8
  params: {
10
9
  path: {
@@ -30,7 +29,7 @@ async function deleteAndWaitCluster(session: Session, projectId: string, cluster
30
29
  }
31
30
  }
32
31
 
33
- describeAtlas("clusters", (integration) => {
32
+ describeWithAtlas("clusters", (integration) => {
34
33
  withProject(integration, ({ getProjectId }) => {
35
34
  const clusterName = "ClusterTest-" + randomId;
36
35
 
@@ -45,11 +44,11 @@ describeAtlas("clusters", (integration) => {
45
44
  describe("atlas-create-free-cluster", () => {
46
45
  it("should have correct metadata", async () => {
47
46
  const { tools } = await integration.mcpClient().listTools();
48
- const createFreeCluster = tools.find((tool) => tool.name === "atlas-create-free-cluster")!;
47
+ const createFreeCluster = tools.find((tool) => tool.name === "atlas-create-free-cluster");
49
48
 
50
- expect(createFreeCluster).toBeDefined();
49
+ expectDefined(createFreeCluster);
51
50
  expect(createFreeCluster.inputSchema.type).toBe("object");
52
- expect(createFreeCluster.inputSchema.properties).toBeDefined();
51
+ expectDefined(createFreeCluster.inputSchema.properties);
53
52
  expect(createFreeCluster.inputSchema.properties).toHaveProperty("projectId");
54
53
  expect(createFreeCluster.inputSchema.properties).toHaveProperty("name");
55
54
  expect(createFreeCluster.inputSchema.properties).toHaveProperty("region");
@@ -75,11 +74,11 @@ describeAtlas("clusters", (integration) => {
75
74
  describe("atlas-inspect-cluster", () => {
76
75
  it("should have correct metadata", async () => {
77
76
  const { tools } = await integration.mcpClient().listTools();
78
- const inspectCluster = tools.find((tool) => tool.name === "atlas-inspect-cluster")!;
77
+ const inspectCluster = tools.find((tool) => tool.name === "atlas-inspect-cluster");
79
78
 
80
- expect(inspectCluster).toBeDefined();
79
+ expectDefined(inspectCluster);
81
80
  expect(inspectCluster.inputSchema.type).toBe("object");
82
- expect(inspectCluster.inputSchema.properties).toBeDefined();
81
+ expectDefined(inspectCluster.inputSchema.properties);
83
82
  expect(inspectCluster.inputSchema.properties).toHaveProperty("projectId");
84
83
  expect(inspectCluster.inputSchema.properties).toHaveProperty("clusterName");
85
84
  });
@@ -100,10 +99,10 @@ describeAtlas("clusters", (integration) => {
100
99
  describe("atlas-list-clusters", () => {
101
100
  it("should have correct metadata", async () => {
102
101
  const { tools } = await integration.mcpClient().listTools();
103
- const listClusters = tools.find((tool) => tool.name === "atlas-list-clusters")!;
104
- expect(listClusters).toBeDefined();
102
+ const listClusters = tools.find((tool) => tool.name === "atlas-list-clusters");
103
+ expectDefined(listClusters);
105
104
  expect(listClusters.inputSchema.type).toBe("object");
106
- expect(listClusters.inputSchema.properties).toBeDefined();
105
+ expectDefined(listClusters.inputSchema.properties);
107
106
  expect(listClusters.inputSchema.properties).toHaveProperty("projectId");
108
107
  });
109
108
 
@@ -1,15 +1,15 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { Session } from "../../../../src/session.js";
3
- import { describeAtlas, withProject, randomId } from "./atlasHelpers.js";
3
+ import { describeWithAtlas, withProject, randomId } from "./atlasHelpers.js";
4
+ import { expectDefined } from "../../helpers.js";
4
5
 
5
- describeAtlas("db users", (integration) => {
6
+ describeWithAtlas("db users", (integration) => {
6
7
  const userName = "testuser-" + randomId;
7
8
  withProject(integration, ({ getProjectId }) => {
8
9
  afterAll(async () => {
9
10
  const projectId = getProjectId();
10
11
 
11
12
  const session: Session = integration.mcpServer().session;
12
- session.ensureAuthenticated();
13
13
  await session.apiClient.deleteDatabaseUser({
14
14
  params: {
15
15
  path: {
@@ -24,10 +24,10 @@ describeAtlas("db users", (integration) => {
24
24
  describe("atlas-create-db-user", () => {
25
25
  it("should have correct metadata", async () => {
26
26
  const { tools } = await integration.mcpClient().listTools();
27
- const createDbUser = tools.find((tool) => tool.name === "atlas-create-db-user")!;
28
- expect(createDbUser).toBeDefined();
27
+ const createDbUser = tools.find((tool) => tool.name === "atlas-create-db-user");
28
+ expectDefined(createDbUser);
29
29
  expect(createDbUser.inputSchema.type).toBe("object");
30
- expect(createDbUser.inputSchema.properties).toBeDefined();
30
+ expectDefined(createDbUser.inputSchema.properties);
31
31
  expect(createDbUser.inputSchema.properties).toHaveProperty("projectId");
32
32
  expect(createDbUser.inputSchema.properties).toHaveProperty("username");
33
33
  expect(createDbUser.inputSchema.properties).toHaveProperty("password");
@@ -59,10 +59,10 @@ describeAtlas("db users", (integration) => {
59
59
  describe("atlas-list-db-users", () => {
60
60
  it("should have correct metadata", async () => {
61
61
  const { tools } = await integration.mcpClient().listTools();
62
- const listDbUsers = tools.find((tool) => tool.name === "atlas-list-db-users")!;
63
- expect(listDbUsers).toBeDefined();
62
+ const listDbUsers = tools.find((tool) => tool.name === "atlas-list-db-users");
63
+ expectDefined(listDbUsers);
64
64
  expect(listDbUsers.inputSchema.type).toBe("object");
65
- expect(listDbUsers.inputSchema.properties).toBeDefined();
65
+ expectDefined(listDbUsers.inputSchema.properties);
66
66
  expect(listDbUsers.inputSchema.properties).toHaveProperty("projectId");
67
67
  });
68
68
  it("returns database users by project", async () => {
@@ -1,13 +1,13 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
- import { setupIntegrationTest } from "../../helpers.js";
3
- import { parseTable, describeAtlas } from "./atlasHelpers.js";
2
+ import { expectDefined } from "../../helpers.js";
3
+ import { parseTable, describeWithAtlas } from "./atlasHelpers.js";
4
4
 
5
- describeAtlas("orgs", (integration) => {
5
+ describeWithAtlas("orgs", (integration) => {
6
6
  describe("atlas-list-orgs", () => {
7
7
  it("should have correct metadata", async () => {
8
8
  const { tools } = await integration.mcpClient().listTools();
9
9
  const listOrgs = tools.find((tool) => tool.name === "atlas-list-orgs");
10
- expect(listOrgs).toBeDefined();
10
+ expectDefined(listOrgs);
11
11
  });
12
12
 
13
13
  it("returns org names", async () => {