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.
- package/.github/workflows/code_health.yaml +53 -4
- package/.github/workflows/prepare_release.yaml +4 -4
- package/README.md +2 -0
- package/dist/common/atlas/apiClient.js +30 -2
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/config.js +2 -7
- package/dist/config.js.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/logger.js +52 -27
- package/dist/logger.js.map +1 -1
- package/dist/packageInfo.js +6 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/server.js +71 -8
- package/dist/server.js.map +1 -1
- package/dist/session.js +17 -12
- package/dist/session.js.map +1 -1
- package/dist/telemetry/constants.js +15 -0
- package/dist/telemetry/constants.js.map +1 -0
- package/dist/telemetry/eventCache.js +53 -0
- package/dist/telemetry/eventCache.js.map +1 -0
- package/dist/telemetry/telemetry.js +96 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/telemetry/types.js +2 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/tools/atlas/atlasTool.js +8 -3
- package/dist/tools/atlas/atlasTool.js.map +1 -1
- package/dist/tools/atlas/createAccessList.js +0 -1
- package/dist/tools/atlas/createAccessList.js.map +1 -1
- package/dist/tools/atlas/createDBUser.js +0 -1
- package/dist/tools/atlas/createDBUser.js.map +1 -1
- package/dist/tools/atlas/createFreeCluster.js +0 -1
- package/dist/tools/atlas/createFreeCluster.js.map +1 -1
- package/dist/tools/atlas/createProject.js +0 -1
- package/dist/tools/atlas/createProject.js.map +1 -1
- package/dist/tools/atlas/inspectAccessList.js +0 -1
- package/dist/tools/atlas/inspectAccessList.js.map +1 -1
- package/dist/tools/atlas/inspectCluster.js +0 -1
- package/dist/tools/atlas/inspectCluster.js.map +1 -1
- package/dist/tools/atlas/listClusters.js +0 -1
- package/dist/tools/atlas/listClusters.js.map +1 -1
- package/dist/tools/atlas/listDBUsers.js +0 -1
- package/dist/tools/atlas/listDBUsers.js.map +1 -1
- package/dist/tools/atlas/listOrgs.js +0 -1
- package/dist/tools/atlas/listOrgs.js.map +1 -1
- package/dist/tools/atlas/listProjects.js +10 -4
- package/dist/tools/atlas/listProjects.js.map +1 -1
- package/dist/tools/mongodb/metadata/collectionSchema.js +15 -13
- package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
- package/dist/tools/mongodb/metadata/collectionStorageSize.js +32 -3
- package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
- package/dist/tools/mongodb/metadata/connect.js +5 -6
- package/dist/tools/mongodb/metadata/connect.js.map +1 -1
- package/dist/tools/mongodb/metadata/dbStats.js +6 -1
- package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
- package/dist/tools/mongodb/metadata/explain.js +14 -7
- package/dist/tools/mongodb/metadata/explain.js.map +1 -1
- package/dist/tools/mongodb/metadata/logs.js +45 -0
- package/dist/tools/mongodb/metadata/logs.js.map +1 -0
- package/dist/tools/mongodb/mongodbTool.js +10 -11
- package/dist/tools/mongodb/mongodbTool.js.map +1 -1
- package/dist/tools/mongodb/read/aggregate.js +3 -3
- package/dist/tools/mongodb/read/aggregate.js.map +1 -1
- package/dist/tools/mongodb/read/collectionIndexes.js +24 -5
- package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
- package/dist/tools/mongodb/read/find.js +3 -2
- package/dist/tools/mongodb/read/find.js.map +1 -1
- package/dist/tools/mongodb/tools.js +2 -0
- package/dist/tools/mongodb/tools.js.map +1 -1
- package/dist/tools/mongodb/update/renameCollection.js +28 -4
- package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
- package/dist/tools/mongodb/update/updateMany.js +5 -7
- package/dist/tools/mongodb/update/updateMany.js.map +1 -1
- package/dist/tools/tool.js +39 -10
- package/dist/tools/tool.js.map +1 -1
- package/eslint.config.js +29 -10
- package/global.d.ts +1 -0
- package/package.json +6 -2
- package/scripts/apply.ts +9 -7
- package/scripts/filter.ts +3 -2
- package/src/common/atlas/apiClient.ts +48 -7
- package/src/config.ts +4 -10
- package/src/index.ts +10 -6
- package/src/logger.ts +66 -28
- package/src/packageInfo.ts +6 -0
- package/src/server.ts +103 -9
- package/src/session.ts +34 -17
- package/src/telemetry/constants.ts +15 -0
- package/src/telemetry/eventCache.ts +62 -0
- package/src/telemetry/telemetry.ts +137 -0
- package/src/telemetry/types.ts +60 -0
- package/src/tools/atlas/atlasTool.ts +7 -5
- package/src/tools/atlas/createAccessList.ts +0 -2
- package/src/tools/atlas/createDBUser.ts +0 -2
- package/src/tools/atlas/createFreeCluster.ts +0 -2
- package/src/tools/atlas/createProject.ts +0 -1
- package/src/tools/atlas/inspectAccessList.ts +0 -2
- package/src/tools/atlas/inspectCluster.ts +0 -2
- package/src/tools/atlas/listClusters.ts +0 -2
- package/src/tools/atlas/listDBUsers.ts +0 -2
- package/src/tools/atlas/listOrgs.ts +0 -2
- package/src/tools/atlas/listProjects.ts +12 -4
- package/src/tools/mongodb/metadata/collectionSchema.ts +16 -14
- package/src/tools/mongodb/metadata/collectionStorageSize.ts +41 -3
- package/src/tools/mongodb/metadata/connect.ts +5 -6
- package/src/tools/mongodb/metadata/dbStats.ts +6 -1
- package/src/tools/mongodb/metadata/explain.ts +20 -7
- package/src/tools/mongodb/metadata/logs.ts +55 -0
- package/src/tools/mongodb/mongodbTool.ts +12 -15
- package/src/tools/mongodb/read/aggregate.ts +3 -3
- package/src/tools/mongodb/read/collectionIndexes.ts +29 -5
- package/src/tools/mongodb/read/find.ts +3 -2
- package/src/tools/mongodb/tools.ts +2 -0
- package/src/tools/mongodb/update/renameCollection.ts +33 -4
- package/src/tools/mongodb/update/updateMany.ts +5 -7
- package/src/tools/tool.ts +51 -15
- package/tests/integration/helpers.ts +84 -107
- package/tests/integration/inMemoryTransport.ts +3 -2
- package/tests/integration/server.test.ts +47 -21
- package/tests/integration/tools/atlas/accessLists.test.ts +13 -15
- package/tests/integration/tools/atlas/atlasHelpers.ts +3 -8
- package/tests/integration/tools/atlas/clusters.test.ts +12 -13
- package/tests/integration/tools/atlas/dbUsers.test.ts +9 -9
- package/tests/integration/tools/atlas/orgs.test.ts +4 -4
- package/tests/integration/tools/atlas/projects.test.ts +10 -12
- package/tests/integration/tools/mongodb/create/createCollection.test.ts +19 -62
- package/tests/integration/tools/mongodb/create/createIndex.test.ts +41 -87
- package/tests/integration/tools/mongodb/create/insertMany.test.ts +35 -78
- package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +25 -62
- package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +22 -71
- package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +29 -63
- package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +154 -0
- package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +86 -0
- package/tests/integration/tools/mongodb/metadata/connect.test.ts +33 -23
- package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +104 -0
- package/tests/integration/tools/mongodb/metadata/explain.test.ts +171 -0
- package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +28 -56
- package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +32 -26
- package/tests/integration/tools/mongodb/metadata/logs.test.ts +83 -0
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +165 -0
- package/tests/integration/tools/mongodb/read/aggregate.test.ts +99 -0
- package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +99 -0
- package/tests/integration/tools/mongodb/read/count.test.ts +31 -79
- package/tests/integration/tools/mongodb/read/find.test.ts +182 -0
- package/tests/integration/tools/mongodb/update/renameCollection.test.ts +194 -0
- package/tests/integration/tools/mongodb/update/updateMany.test.ts +238 -0
- package/tsconfig.jest.json +2 -1
- package/tsconfig.json +1 -1
- 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
|
|
5
|
-
import
|
|
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 {
|
|
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(
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
5
|
+
describe("without atlas", () => {
|
|
6
|
+
const integration = setupIntegrationTest({
|
|
7
|
+
...config,
|
|
8
|
+
apiClientId: undefined,
|
|
9
|
+
apiClientSecret: undefined,
|
|
10
|
+
});
|
|
5
11
|
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
21
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
45
|
+
const createAccessList = tools.find((tool) => tool.name === "atlas-create-access-list");
|
|
46
|
+
expectDefined(createAccessList);
|
|
49
47
|
expect(createAccessList.inputSchema.type).toBe("object");
|
|
50
|
-
|
|
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
|
-
|
|
77
|
+
const inspectAccessList = tools.find((tool) => tool.name === "atlas-inspect-access-list");
|
|
78
|
+
expectDefined(inspectAccessList);
|
|
81
79
|
expect(inspectAccessList.inputSchema.type).toBe("object");
|
|
82
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
expectDefined(createFreeCluster);
|
|
51
50
|
expect(createFreeCluster.inputSchema.type).toBe("object");
|
|
52
|
-
|
|
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
|
-
|
|
79
|
+
expectDefined(inspectCluster);
|
|
81
80
|
expect(inspectCluster.inputSchema.type).toBe("object");
|
|
82
|
-
|
|
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
|
-
|
|
102
|
+
const listClusters = tools.find((tool) => tool.name === "atlas-list-clusters");
|
|
103
|
+
expectDefined(listClusters);
|
|
105
104
|
expect(listClusters.inputSchema.type).toBe("object");
|
|
106
|
-
|
|
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 {
|
|
3
|
+
import { describeWithAtlas, withProject, randomId } from "./atlasHelpers.js";
|
|
4
|
+
import { expectDefined } from "../../helpers.js";
|
|
4
5
|
|
|
5
|
-
|
|
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
|
-
|
|
27
|
+
const createDbUser = tools.find((tool) => tool.name === "atlas-create-db-user");
|
|
28
|
+
expectDefined(createDbUser);
|
|
29
29
|
expect(createDbUser.inputSchema.type).toBe("object");
|
|
30
|
-
|
|
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
|
-
|
|
62
|
+
const listDbUsers = tools.find((tool) => tool.name === "atlas-list-db-users");
|
|
63
|
+
expectDefined(listDbUsers);
|
|
64
64
|
expect(listDbUsers.inputSchema.type).toBe("object");
|
|
65
|
-
|
|
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 {
|
|
3
|
-
import { parseTable,
|
|
2
|
+
import { expectDefined } from "../../helpers.js";
|
|
3
|
+
import { parseTable, describeWithAtlas } from "./atlasHelpers.js";
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
10
|
+
expectDefined(listOrgs);
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("returns org names", async () => {
|