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