mongodb-mcp-server 0.1.1 → 0.1.3
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/.dockerignore +11 -0
- package/.github/pull_request_template.md +5 -0
- package/.github/workflows/check-pr-title.yml +29 -0
- package/.github/workflows/code_health.yaml +3 -3
- package/.github/workflows/docker.yaml +57 -0
- package/.github/workflows/stale.yml +32 -0
- package/.smithery/Dockerfile +30 -0
- package/.smithery/smithery.yaml +73 -0
- package/CONTRIBUTING.md +1 -1
- package/Dockerfile +10 -0
- package/README.md +150 -15
- package/dist/common/atlas/apiClient.js +10 -1
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/common/atlas/cluster.js +1 -1
- package/dist/common/atlas/cluster.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/helpers/indexCheck.js +63 -0
- package/dist/helpers/indexCheck.js.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/tools/atlas/create/createProject.js +5 -1
- package/dist/tools/atlas/create/createProject.js.map +1 -1
- package/dist/tools/atlas/read/listAlerts.js +41 -0
- package/dist/tools/atlas/read/listAlerts.js.map +1 -0
- package/dist/tools/atlas/read/listProjects.js +3 -1
- package/dist/tools/atlas/read/listProjects.js.map +1 -1
- package/dist/tools/atlas/tools.js +2 -0
- package/dist/tools/atlas/tools.js.map +1 -1
- package/dist/tools/mongodb/delete/deleteMany.js +18 -0
- package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
- package/dist/tools/mongodb/metadata/explain.js +1 -1
- package/dist/tools/mongodb/metadata/explain.js.map +1 -1
- package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -1
- package/dist/tools/mongodb/mongodbTool.js +10 -0
- package/dist/tools/mongodb/mongodbTool.js.map +1 -1
- package/dist/tools/mongodb/read/aggregate.js +9 -0
- package/dist/tools/mongodb/read/aggregate.js.map +1 -1
- package/dist/tools/mongodb/read/count.js +15 -2
- package/dist/tools/mongodb/read/count.js.map +1 -1
- package/dist/tools/mongodb/read/find.js +7 -0
- package/dist/tools/mongodb/read/find.js.map +1 -1
- package/dist/tools/mongodb/update/updateMany.js +20 -0
- package/dist/tools/mongodb/update/updateMany.js.map +1 -1
- package/dist/tools/tool.js +34 -2
- package/dist/tools/tool.js.map +1 -1
- package/package.json +9 -9
- package/scripts/apply.ts +4 -4
- package/scripts/filter.ts +1 -0
- package/src/common/atlas/apiClient.ts +11 -1
- package/src/common/atlas/cluster.ts +1 -2
- package/src/common/atlas/openapi.d.ts +1242 -28
- package/src/config.ts +2 -0
- package/src/errors.ts +1 -0
- package/src/helpers/indexCheck.ts +83 -0
- package/src/index.ts +20 -0
- package/src/logger.ts +5 -0
- package/src/tools/atlas/create/createProject.ts +7 -1
- package/src/tools/atlas/read/listAlerts.ts +45 -0
- package/src/tools/atlas/read/listProjects.ts +4 -2
- package/src/tools/atlas/tools.ts +2 -0
- package/src/tools/mongodb/delete/deleteMany.ts +20 -0
- package/src/tools/mongodb/metadata/explain.ts +1 -1
- package/src/tools/mongodb/metadata/listDatabases.ts +0 -1
- package/src/tools/mongodb/mongodbTool.ts +10 -0
- package/src/tools/mongodb/read/aggregate.ts +11 -0
- package/src/tools/mongodb/read/count.ts +18 -2
- package/src/tools/mongodb/read/find.ts +9 -0
- package/src/tools/mongodb/update/updateMany.ts +22 -0
- package/src/tools/tool.ts +40 -3
- package/tests/integration/helpers.ts +23 -0
- package/tests/integration/indexCheck.test.ts +463 -0
- package/tests/integration/server.test.ts +5 -4
- package/tests/integration/tools/atlas/accessLists.test.ts +2 -2
- package/tests/integration/tools/atlas/alerts.test.ts +42 -0
- package/tests/integration/tools/atlas/atlasHelpers.ts +5 -3
- package/tests/integration/tools/atlas/clusters.test.ts +4 -4
- package/tests/integration/tools/atlas/dbUsers.test.ts +7 -7
- package/tests/integration/tools/atlas/orgs.test.ts +2 -2
- package/tests/integration/tools/atlas/projects.test.ts +3 -3
- package/tests/integration/tools/mongodb/create/createCollection.test.ts +2 -2
- package/tests/integration/tools/mongodb/create/createIndex.test.ts +2 -2
- package/tests/integration/tools/mongodb/create/insertMany.test.ts +1 -1
- package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +1 -1
- package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +2 -2
- package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +4 -4
- package/tests/integration/tools/mongodb/metadata/explain.test.ts +10 -10
- package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +1 -1
- package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +9 -5
- package/tests/integration/tools/mongodb/metadata/logs.test.ts +4 -4
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +1 -0
- package/tests/integration/tools/mongodb/read/aggregate.test.ts +22 -7
- package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +5 -5
- package/tests/integration/tools/mongodb/read/count.test.ts +15 -10
- package/tests/integration/tools/mongodb/read/find.test.ts +6 -6
- package/tests/integration/tools/mongodb/update/renameCollection.test.ts +4 -4
- package/tests/unit/EJsonTransport.test.ts +1 -1
- package/tests/unit/indexCheck.test.ts +149 -0
- package/tests/unit/session.test.ts +1 -1
- package/tsconfig.build.json +1 -0
|
@@ -43,7 +43,7 @@ describeWithAtlas("projects", (integration) => {
|
|
|
43
43
|
})) as CallToolResult;
|
|
44
44
|
expect(response.content).toBeArray();
|
|
45
45
|
expect(response.content).toHaveLength(1);
|
|
46
|
-
expect(response.content[0]
|
|
46
|
+
expect(response.content[0]?.text).toContain(projName);
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
describe("atlas-list-projects", () => {
|
|
@@ -62,8 +62,8 @@ describeWithAtlas("projects", (integration) => {
|
|
|
62
62
|
.callTool({ name: "atlas-list-projects", arguments: {} })) as CallToolResult;
|
|
63
63
|
expect(response.content).toBeArray();
|
|
64
64
|
expect(response.content).toHaveLength(1);
|
|
65
|
-
expect(response.content[0]
|
|
66
|
-
const data = parseTable(response.content[0]
|
|
65
|
+
expect(response.content[0]?.text).toContain(projName);
|
|
66
|
+
const data = parseTable(response.content[0]?.text as string);
|
|
67
67
|
expect(data).toBeArray();
|
|
68
68
|
expect(data.length).toBeGreaterThan(0);
|
|
69
69
|
let found = false;
|
|
@@ -34,7 +34,7 @@ describeWithMongoDB("createCollection tool", (integration) => {
|
|
|
34
34
|
|
|
35
35
|
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
|
|
36
36
|
expect(collections).toHaveLength(1);
|
|
37
|
-
expect(collections[0]
|
|
37
|
+
expect(collections[0]?.name).toEqual("bar");
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -78,7 +78,7 @@ describeWithMongoDB("createCollection tool", (integration) => {
|
|
|
78
78
|
expect(content).toEqual(`Collection "collection1" created in database "${integration.randomDbName()}".`);
|
|
79
79
|
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
|
|
80
80
|
expect(collections).toHaveLength(1);
|
|
81
|
-
expect(collections[0]
|
|
81
|
+
expect(collections[0]?.name).toEqual("collection1");
|
|
82
82
|
|
|
83
83
|
// Make sure we didn't drop the existing collection
|
|
84
84
|
documents = await mongoClient.db(integration.randomDbName()).collection("collection1").find({}).toArray();
|
|
@@ -38,10 +38,10 @@ describeWithMongoDB("createIndex tool", (integration) => {
|
|
|
38
38
|
const mongoClient = integration.mongoClient();
|
|
39
39
|
const collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
|
|
40
40
|
expect(collections).toHaveLength(1);
|
|
41
|
-
expect(collections[0]
|
|
41
|
+
expect(collections[0]?.name).toEqual("coll1");
|
|
42
42
|
const indexes = await mongoClient.db(integration.randomDbName()).collection(collection).indexes();
|
|
43
43
|
expect(indexes).toHaveLength(expected.length + 1);
|
|
44
|
-
expect(indexes[0]
|
|
44
|
+
expect(indexes[0]?.name).toEqual("_id_");
|
|
45
45
|
for (const index of expected) {
|
|
46
46
|
const foundIndex = indexes.find((i) => i.name === index.name);
|
|
47
47
|
expectDefined(foundIndex);
|
|
@@ -82,7 +82,7 @@ describeWithMongoDB("insertMany tool", (integration) => {
|
|
|
82
82
|
const content = getResponseContent(response.content);
|
|
83
83
|
expect(content).toContain("Error running insert-many");
|
|
84
84
|
expect(content).toContain("duplicate key error");
|
|
85
|
-
expect(content).toContain(insertedIds[0]
|
|
85
|
+
expect(content).toContain(insertedIds[0]?.toString());
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
validateAutoConnectBehavior(integration, "insert-many", () => {
|
|
@@ -54,7 +54,7 @@ describeWithMongoDB("dropCollection tool", (integration) => {
|
|
|
54
54
|
);
|
|
55
55
|
const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
|
|
56
56
|
expect(collections).toHaveLength(1);
|
|
57
|
-
expect(collections[0]
|
|
57
|
+
expect(collections[0]?.name).toBe("coll2");
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
validateAutoConnectBehavior(integration, "drop-collection", () => {
|
|
@@ -132,11 +132,11 @@ describeWithMongoDB("collectionSchema tool", (integration) => {
|
|
|
132
132
|
expect(items).toHaveLength(2);
|
|
133
133
|
|
|
134
134
|
// Expect to find _id, name, age
|
|
135
|
-
expect(items[0]
|
|
135
|
+
expect(items[0]?.text).toEqual(
|
|
136
136
|
`Found ${Object.entries(testCase.expectedSchema).length} fields in the schema for "${integration.randomDbName()}.foo"`
|
|
137
137
|
);
|
|
138
138
|
|
|
139
|
-
const schema = JSON.parse(items[1]
|
|
139
|
+
const schema = JSON.parse(items[1]?.text ?? "{}") as SimplifiedSchema;
|
|
140
140
|
expect(schema).toEqual(testCase.expectedSchema);
|
|
141
141
|
});
|
|
142
142
|
}
|
|
@@ -28,9 +28,9 @@ describeWithMongoDB("dbStats tool", (integration) => {
|
|
|
28
28
|
});
|
|
29
29
|
const elements = getResponseElements(response.content);
|
|
30
30
|
expect(elements).toHaveLength(2);
|
|
31
|
-
expect(elements[0]
|
|
31
|
+
expect(elements[0]?.text).toBe(`Statistics for database ${integration.randomDbName()}`);
|
|
32
32
|
|
|
33
|
-
const stats = JSON.parse(elements[1]
|
|
33
|
+
const stats = JSON.parse(elements[1]?.text ?? "{}") as {
|
|
34
34
|
db: string;
|
|
35
35
|
collections: number;
|
|
36
36
|
storageSize: number;
|
|
@@ -75,9 +75,9 @@ describeWithMongoDB("dbStats tool", (integration) => {
|
|
|
75
75
|
});
|
|
76
76
|
const elements = getResponseElements(response.content);
|
|
77
77
|
expect(elements).toHaveLength(2);
|
|
78
|
-
expect(elements[0]
|
|
78
|
+
expect(elements[0]?.text).toBe(`Statistics for database ${integration.randomDbName()}`);
|
|
79
79
|
|
|
80
|
-
const stats = JSON.parse(elements[1]
|
|
80
|
+
const stats = JSON.parse(elements[1]?.text ?? "{}") as {
|
|
81
81
|
db: string;
|
|
82
82
|
collections: unknown;
|
|
83
83
|
storageSize: unknown;
|
|
@@ -89,12 +89,12 @@ describeWithMongoDB("explain tool", (integration) => {
|
|
|
89
89
|
|
|
90
90
|
const content = getResponseElements(response.content);
|
|
91
91
|
expect(content).toHaveLength(2);
|
|
92
|
-
expect(content[0]
|
|
92
|
+
expect(content[0]?.text).toEqual(
|
|
93
93
|
`Here is some information about the winning plan chosen by the query optimizer for running the given \`${testCase.method}\` operation in "${integration.randomDbName()}.coll1". This information can be used to understand how the query was executed and to optimize the query performance.`
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
expect(content[1]
|
|
97
|
-
expect(content[1]
|
|
96
|
+
expect(content[1]?.text).toContain("queryPlanner");
|
|
97
|
+
expect(content[1]?.text).toContain("winningPlan");
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
});
|
|
@@ -139,22 +139,22 @@ describeWithMongoDB("explain tool", (integration) => {
|
|
|
139
139
|
|
|
140
140
|
const content = getResponseElements(response.content);
|
|
141
141
|
expect(content).toHaveLength(2);
|
|
142
|
-
expect(content[0]
|
|
142
|
+
expect(content[0]?.text).toEqual(
|
|
143
143
|
`Here is some information about the winning plan chosen by the query optimizer for running the given \`${testCase.method}\` operation in "${integration.randomDbName()}.people". This information can be used to understand how the query was executed and to optimize the query performance.`
|
|
144
144
|
);
|
|
145
145
|
|
|
146
|
-
expect(content[1]
|
|
147
|
-
expect(content[1]
|
|
146
|
+
expect(content[1]?.text).toContain("queryPlanner");
|
|
147
|
+
expect(content[1]?.text).toContain("winningPlan");
|
|
148
148
|
|
|
149
149
|
if (indexed) {
|
|
150
150
|
if (testCase.method === "count") {
|
|
151
|
-
expect(content[1]
|
|
151
|
+
expect(content[1]?.text).toContain("COUNT_SCAN");
|
|
152
152
|
} else {
|
|
153
|
-
expect(content[1]
|
|
153
|
+
expect(content[1]?.text).toContain("IXSCAN");
|
|
154
154
|
}
|
|
155
|
-
expect(content[1]
|
|
155
|
+
expect(content[1]?.text).toContain("name_1");
|
|
156
156
|
} else {
|
|
157
|
-
expect(content[1]
|
|
157
|
+
expect(content[1]?.text).toContain("COLLSCAN");
|
|
158
158
|
}
|
|
159
159
|
});
|
|
160
160
|
}
|
|
@@ -45,7 +45,7 @@ describeWithMongoDB("listCollections tool", (integration) => {
|
|
|
45
45
|
});
|
|
46
46
|
const items = getResponseElements(response.content);
|
|
47
47
|
expect(items).toHaveLength(1);
|
|
48
|
-
expect(items[0]
|
|
48
|
+
expect(items[0]?.text).toContain('Name: "collection-1"');
|
|
49
49
|
|
|
50
50
|
await mongoClient.db(integration.randomDbName()).createCollection("collection-2");
|
|
51
51
|
|
|
@@ -65,9 +65,13 @@ describeWithMongoDB("listDatabases tool", (integration) => {
|
|
|
65
65
|
|
|
66
66
|
function getDbNames(content: unknown): (string | null)[] {
|
|
67
67
|
const responseItems = getResponseElements(content);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
return responseItems
|
|
69
|
+
.map((item) => {
|
|
70
|
+
if (item && typeof item.text === "string") {
|
|
71
|
+
const match = item.text.match(/Name: ([^,]+), Size: \d+ bytes/);
|
|
72
|
+
return match ? match[1] : null;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
})
|
|
76
|
+
.filter((item): item is string | null => item !== undefined);
|
|
73
77
|
}
|
|
@@ -37,11 +37,11 @@ describeWithMongoDB("logs tool", (integration) => {
|
|
|
37
37
|
|
|
38
38
|
// Default limit is 50
|
|
39
39
|
expect(elements.length).toBeLessThanOrEqual(51);
|
|
40
|
-
expect(elements[0]
|
|
40
|
+
expect(elements[0]?.text).toMatch(/Found: \d+ messages/);
|
|
41
41
|
|
|
42
42
|
for (let i = 1; i < elements.length; i++) {
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
44
|
-
const log = JSON.parse(elements[i]
|
|
44
|
+
const log = JSON.parse(elements[i]?.text ?? "{}");
|
|
45
45
|
expect(log).toHaveProperty("t");
|
|
46
46
|
expect(log).toHaveProperty("msg");
|
|
47
47
|
}
|
|
@@ -59,7 +59,7 @@ describeWithMongoDB("logs tool", (integration) => {
|
|
|
59
59
|
const elements = getResponseElements(response);
|
|
60
60
|
expect(elements.length).toBeLessThanOrEqual(51);
|
|
61
61
|
for (let i = 1; i < elements.length; i++) {
|
|
62
|
-
const log = JSON.parse(elements[i]
|
|
62
|
+
const log = JSON.parse(elements[i]?.text ?? "{}") as { tags: string[] };
|
|
63
63
|
expect(log).toHaveProperty("t");
|
|
64
64
|
expect(log).toHaveProperty("msg");
|
|
65
65
|
expect(log).toHaveProperty("tags");
|
|
@@ -76,7 +76,7 @@ describeWithMongoDB("logs tool", (integration) => {
|
|
|
76
76
|
validate: (content) => {
|
|
77
77
|
const elements = getResponseElements(content);
|
|
78
78
|
expect(elements.length).toBeLessThanOrEqual(51);
|
|
79
|
-
expect(elements[0]
|
|
79
|
+
expect(elements[0]?.text).toMatch(/Found: \d+ messages/);
|
|
80
80
|
},
|
|
81
81
|
};
|
|
82
82
|
});
|
|
@@ -35,7 +35,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
|
|
|
35
35
|
|
|
36
36
|
const elements = getResponseElements(response.content);
|
|
37
37
|
expect(elements).toHaveLength(1);
|
|
38
|
-
expect(elements[0]
|
|
38
|
+
expect(elements[0]?.text).toEqual('Found 0 documents in the collection "people":');
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it("can run aggragation on an empty collection", async () => {
|
|
@@ -53,7 +53,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
|
|
|
53
53
|
|
|
54
54
|
const elements = getResponseElements(response.content);
|
|
55
55
|
expect(elements).toHaveLength(1);
|
|
56
|
-
expect(elements[0]
|
|
56
|
+
expect(elements[0]?.text).toEqual('Found 0 documents in the collection "people":');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it("can run aggragation on an existing collection", async () => {
|
|
@@ -79,11 +79,21 @@ describeWithMongoDB("aggregate tool", (integration) => {
|
|
|
79
79
|
|
|
80
80
|
const elements = getResponseElements(response.content);
|
|
81
81
|
expect(elements).toHaveLength(3);
|
|
82
|
-
expect(elements[0]
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
expect(elements[0]?.text).toEqual('Found 2 documents in the collection "people":');
|
|
83
|
+
expect(asObject(JSON.parse(elements[1]?.text ?? "{}"))).toEqual(
|
|
84
|
+
expect.objectContaining({
|
|
85
|
+
_id: expect.any(Object) as object,
|
|
86
|
+
name: "Søren",
|
|
87
|
+
age: 15,
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
expect(asObject(JSON.parse(elements[2]?.text ?? "{}"))).toEqual(
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
_id: expect.any(Object) as object,
|
|
93
|
+
name: "Laura",
|
|
94
|
+
age: 10,
|
|
95
|
+
})
|
|
96
|
+
);
|
|
87
97
|
});
|
|
88
98
|
|
|
89
99
|
validateAutoConnectBehavior(integration, "aggregate", () => {
|
|
@@ -97,3 +107,8 @@ describeWithMongoDB("aggregate tool", (integration) => {
|
|
|
97
107
|
};
|
|
98
108
|
});
|
|
99
109
|
});
|
|
110
|
+
|
|
111
|
+
function asObject(val: unknown): Record<string, unknown> {
|
|
112
|
+
if (typeof val === "object" && val !== null) return val as Record<string, unknown>;
|
|
113
|
+
throw new Error("Expected an object");
|
|
114
|
+
}
|
|
@@ -28,7 +28,7 @@ describeWithMongoDB("collectionIndexes tool", (integration) => {
|
|
|
28
28
|
|
|
29
29
|
const elements = getResponseElements(response.content);
|
|
30
30
|
expect(elements).toHaveLength(1);
|
|
31
|
-
expect(elements[0]
|
|
31
|
+
expect(elements[0]?.text).toEqual(
|
|
32
32
|
'The indexes for "non-existent.people" cannot be determined because the collection does not exist.'
|
|
33
33
|
);
|
|
34
34
|
});
|
|
@@ -47,8 +47,8 @@ describeWithMongoDB("collectionIndexes tool", (integration) => {
|
|
|
47
47
|
|
|
48
48
|
const elements = getResponseElements(response.content);
|
|
49
49
|
expect(elements).toHaveLength(2);
|
|
50
|
-
expect(elements[0]
|
|
51
|
-
expect(elements[1]
|
|
50
|
+
expect(elements[0]?.text).toEqual('Found 1 indexes in the collection "people":');
|
|
51
|
+
expect(elements[1]?.text).toEqual('Name "_id_", definition: {"_id":1}');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it("returns all indexes for a collection", async () => {
|
|
@@ -74,8 +74,8 @@ describeWithMongoDB("collectionIndexes tool", (integration) => {
|
|
|
74
74
|
|
|
75
75
|
const elements = getResponseElements(response.content);
|
|
76
76
|
expect(elements).toHaveLength(indexTypes.length + 2);
|
|
77
|
-
expect(elements[0]
|
|
78
|
-
expect(elements[1]
|
|
77
|
+
expect(elements[0]?.text).toEqual(`Found ${indexTypes.length + 1} indexes in the collection "people":`);
|
|
78
|
+
expect(elements[1]?.text).toEqual('Name "_id_", definition: {"_id":1}');
|
|
79
79
|
|
|
80
80
|
for (const indexType of indexTypes) {
|
|
81
81
|
const index = elements.find((element) => element.text.includes(`prop_${indexType}`));
|
|
@@ -8,16 +8,21 @@ import {
|
|
|
8
8
|
} from "../../../helpers.js";
|
|
9
9
|
|
|
10
10
|
describeWithMongoDB("count tool", (integration) => {
|
|
11
|
-
validateToolMetadata(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
validateToolMetadata(
|
|
12
|
+
integration,
|
|
13
|
+
"count",
|
|
14
|
+
"Gets the number of documents in a MongoDB collection using db.collection.count() and query as an optional filter parameter",
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
name: "query",
|
|
18
|
+
description:
|
|
19
|
+
"A filter/query parameter. Allows users to filter the documents to count. Matches the syntax of the filter argument of db.collection.count().",
|
|
20
|
+
type: "object",
|
|
21
|
+
required: false,
|
|
22
|
+
},
|
|
23
|
+
...databaseCollectionParameters,
|
|
24
|
+
]
|
|
25
|
+
);
|
|
21
26
|
|
|
22
27
|
validateThrowsForInvalidArguments(integration, "count", [
|
|
23
28
|
{},
|
|
@@ -149,10 +149,10 @@ describeWithMongoDB("find tool", (integration) => {
|
|
|
149
149
|
});
|
|
150
150
|
const elements = getResponseElements(response.content);
|
|
151
151
|
expect(elements).toHaveLength(expected.length + 1);
|
|
152
|
-
expect(elements[0]
|
|
152
|
+
expect(elements[0]?.text).toEqual(`Found ${expected.length} documents in the collection "foo":`);
|
|
153
153
|
|
|
154
154
|
for (let i = 0; i < expected.length; i++) {
|
|
155
|
-
expect(JSON.parse(elements[i + 1]
|
|
155
|
+
expect(JSON.parse(elements[i + 1]?.text ?? "{}")).toEqual(expected[i]);
|
|
156
156
|
}
|
|
157
157
|
});
|
|
158
158
|
}
|
|
@@ -165,11 +165,11 @@ describeWithMongoDB("find tool", (integration) => {
|
|
|
165
165
|
});
|
|
166
166
|
const elements = getResponseElements(response.content);
|
|
167
167
|
expect(elements).toHaveLength(11);
|
|
168
|
-
expect(elements[0]
|
|
168
|
+
expect(elements[0]?.text).toEqual('Found 10 documents in the collection "foo":');
|
|
169
169
|
|
|
170
170
|
for (let i = 0; i < 10; i++) {
|
|
171
171
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
172
|
-
expect(JSON.parse(elements[i + 1]
|
|
172
|
+
expect(JSON.parse(elements[i + 1]?.text ?? "{}").value).toEqual(i);
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
@@ -194,10 +194,10 @@ describeWithMongoDB("find tool", (integration) => {
|
|
|
194
194
|
|
|
195
195
|
const elements = getResponseElements(response.content);
|
|
196
196
|
expect(elements).toHaveLength(2);
|
|
197
|
-
expect(elements[0]
|
|
197
|
+
expect(elements[0]?.text).toEqual('Found 1 documents in the collection "foo":');
|
|
198
198
|
|
|
199
199
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
200
|
-
expect(JSON.parse(elements[1]
|
|
200
|
+
expect(JSON.parse(elements[1]?.text ?? "{}").value).toEqual(fooObject.value);
|
|
201
201
|
});
|
|
202
202
|
});
|
|
203
203
|
|
|
@@ -94,7 +94,7 @@ describeWithMongoDB("renameCollection tool", (integration) => {
|
|
|
94
94
|
.find({})
|
|
95
95
|
.toArray();
|
|
96
96
|
expect(docsInAfter).toHaveLength(1);
|
|
97
|
-
expect(docsInAfter[0]
|
|
97
|
+
expect(docsInAfter[0]?.value).toEqual(42);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
it("returns an error when renaming to an existing collection", async () => {
|
|
@@ -123,7 +123,7 @@ describeWithMongoDB("renameCollection tool", (integration) => {
|
|
|
123
123
|
.find({})
|
|
124
124
|
.toArray();
|
|
125
125
|
expect(docsInBefore).toHaveLength(1);
|
|
126
|
-
expect(docsInBefore[0]
|
|
126
|
+
expect(docsInBefore[0]?.value).toEqual(42);
|
|
127
127
|
|
|
128
128
|
const docsInAfter = await integration
|
|
129
129
|
.mongoClient()
|
|
@@ -132,7 +132,7 @@ describeWithMongoDB("renameCollection tool", (integration) => {
|
|
|
132
132
|
.find({})
|
|
133
133
|
.toArray();
|
|
134
134
|
expect(docsInAfter).toHaveLength(1);
|
|
135
|
-
expect(docsInAfter[0]
|
|
135
|
+
expect(docsInAfter[0]?.value).toEqual(84);
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
it("renames to existing collection with dropTarget", async () => {
|
|
@@ -174,7 +174,7 @@ describeWithMongoDB("renameCollection tool", (integration) => {
|
|
|
174
174
|
.find({})
|
|
175
175
|
.toArray();
|
|
176
176
|
expect(docsInAfter).toHaveLength(1);
|
|
177
|
-
expect(docsInAfter[0]
|
|
177
|
+
expect(docsInAfter[0]?.value).toEqual(42);
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { usesIndex, getIndexCheckErrorMessage } from "../../src/helpers/indexCheck.js";
|
|
2
|
+
import { Document } from "mongodb";
|
|
3
|
+
|
|
4
|
+
describe("indexCheck", () => {
|
|
5
|
+
describe("usesIndex", () => {
|
|
6
|
+
it("should return true for IXSCAN", () => {
|
|
7
|
+
const explainResult: Document = {
|
|
8
|
+
queryPlanner: {
|
|
9
|
+
winningPlan: {
|
|
10
|
+
stage: "IXSCAN",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should return true for COUNT_SCAN", () => {
|
|
18
|
+
const explainResult: Document = {
|
|
19
|
+
queryPlanner: {
|
|
20
|
+
winningPlan: {
|
|
21
|
+
stage: "COUNT_SCAN",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return true for IDHACK", () => {
|
|
29
|
+
const explainResult: Document = {
|
|
30
|
+
queryPlanner: {
|
|
31
|
+
winningPlan: {
|
|
32
|
+
stage: "IDHACK",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return true for EXPRESS_IXSCAN (MongoDB 8.0+)", () => {
|
|
40
|
+
const explainResult: Document = {
|
|
41
|
+
queryPlanner: {
|
|
42
|
+
winningPlan: {
|
|
43
|
+
stage: "EXPRESS_IXSCAN",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should return true for EXPRESS_CLUSTERED_IXSCAN (MongoDB 8.0+)", () => {
|
|
51
|
+
const explainResult: Document = {
|
|
52
|
+
queryPlanner: {
|
|
53
|
+
winningPlan: {
|
|
54
|
+
stage: "EXPRESS_CLUSTERED_IXSCAN",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should return true for EXPRESS_UPDATE (MongoDB 8.0+)", () => {
|
|
62
|
+
const explainResult: Document = {
|
|
63
|
+
queryPlanner: {
|
|
64
|
+
winningPlan: {
|
|
65
|
+
stage: "EXPRESS_UPDATE",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should return true for EXPRESS_DELETE (MongoDB 8.0+)", () => {
|
|
73
|
+
const explainResult: Document = {
|
|
74
|
+
queryPlanner: {
|
|
75
|
+
winningPlan: {
|
|
76
|
+
stage: "EXPRESS_DELETE",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should return false for COLLSCAN", () => {
|
|
84
|
+
const explainResult: Document = {
|
|
85
|
+
queryPlanner: {
|
|
86
|
+
winningPlan: {
|
|
87
|
+
stage: "COLLSCAN",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
expect(usesIndex(explainResult)).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should return true for nested IXSCAN in inputStage", () => {
|
|
95
|
+
const explainResult: Document = {
|
|
96
|
+
queryPlanner: {
|
|
97
|
+
winningPlan: {
|
|
98
|
+
stage: "LIMIT",
|
|
99
|
+
inputStage: {
|
|
100
|
+
stage: "IXSCAN",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return true for nested EXPRESS_IXSCAN in inputStage", () => {
|
|
109
|
+
const explainResult: Document = {
|
|
110
|
+
queryPlanner: {
|
|
111
|
+
winningPlan: {
|
|
112
|
+
stage: "SORT",
|
|
113
|
+
inputStage: {
|
|
114
|
+
stage: "EXPRESS_IXSCAN",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
expect(usesIndex(explainResult)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should return false for unknown stage types", () => {
|
|
123
|
+
const explainResult: Document = {
|
|
124
|
+
queryPlanner: {
|
|
125
|
+
winningPlan: {
|
|
126
|
+
stage: "UNKNOWN_STAGE",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
expect(usesIndex(explainResult)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should handle missing queryPlanner", () => {
|
|
134
|
+
const explainResult: Document = {};
|
|
135
|
+
expect(usesIndex(explainResult)).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("getIndexCheckErrorMessage", () => {
|
|
140
|
+
it("should generate appropriate error message", () => {
|
|
141
|
+
const message = getIndexCheckErrorMessage("testdb", "testcoll", "find");
|
|
142
|
+
expect(message).toContain("Index check failed");
|
|
143
|
+
expect(message).toContain("testdb.testcoll");
|
|
144
|
+
expect(message).toContain("find operation");
|
|
145
|
+
expect(message).toContain("collection scan (COLLSCAN)");
|
|
146
|
+
expect(message).toContain("MDB_MCP_INDEX_CHECK");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -53,7 +53,7 @@ describe("Session", () => {
|
|
|
53
53
|
typeof NodeDriverServiceProvider.connect
|
|
54
54
|
>;
|
|
55
55
|
expect(connectMock).toHaveBeenCalledOnce();
|
|
56
|
-
const connectionString = connectMock.mock.calls[0][0];
|
|
56
|
+
const connectionString = connectMock.mock.calls[0]?.[0];
|
|
57
57
|
if (testCase.expectAppName) {
|
|
58
58
|
expect(connectionString).toContain("appName=MongoDB+MCP+Server");
|
|
59
59
|
} else {
|