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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
3
|
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getSimplifiedSchema } from "mongodb-schema";
|
|
5
5
|
|
|
6
6
|
export class CollectionSchemaTool extends MongoDBToolBase {
|
|
7
7
|
protected name = "collection-schema";
|
|
@@ -13,29 +13,31 @@ export class CollectionSchemaTool extends MongoDBToolBase {
|
|
|
13
13
|
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
|
|
14
14
|
const provider = await this.ensureConnected();
|
|
15
15
|
const documents = await provider.find(database, collection, {}, { limit: 5 }).toArray();
|
|
16
|
-
const schema = await
|
|
16
|
+
const schema = await getSimplifiedSchema(documents);
|
|
17
|
+
|
|
18
|
+
const fieldsCount = Object.entries(schema).length;
|
|
19
|
+
if (fieldsCount === 0) {
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
text: `Could not deduce the schema for "${database}.${collection}". This may be because it doesn't exist or is empty.`,
|
|
24
|
+
type: "text",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
return {
|
|
19
31
|
content: [
|
|
20
32
|
{
|
|
21
|
-
text: `Found ${
|
|
33
|
+
text: `Found ${fieldsCount} fields in the schema for "${database}.${collection}"`,
|
|
22
34
|
type: "text",
|
|
23
35
|
},
|
|
24
36
|
{
|
|
25
|
-
text:
|
|
37
|
+
text: JSON.stringify(schema),
|
|
26
38
|
type: "text",
|
|
27
39
|
},
|
|
28
40
|
],
|
|
29
41
|
};
|
|
30
42
|
}
|
|
31
|
-
|
|
32
|
-
private formatFieldOutput(fields: SchemaField[]): string {
|
|
33
|
-
let result = "| Field | Type | Confidence |\n";
|
|
34
|
-
result += "|-------|------|-------------|\n";
|
|
35
|
-
for (const field of fields) {
|
|
36
|
-
const fieldType = Array.isArray(field.type) ? field.type.join(", ") : field.type;
|
|
37
|
-
result += `| ${field.name} | \`${fieldType}\` | ${(field.probability * 100).toFixed(0)}% |\n`;
|
|
38
|
-
}
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
43
|
}
|
|
@@ -4,7 +4,7 @@ import { ToolArgs, OperationType } from "../../tool.js";
|
|
|
4
4
|
|
|
5
5
|
export class CollectionStorageSizeTool extends MongoDBToolBase {
|
|
6
6
|
protected name = "collection-storage-size";
|
|
7
|
-
protected description = "Gets the size of the collection
|
|
7
|
+
protected description = "Gets the size of the collection";
|
|
8
8
|
protected argsShape = DbOperationArgs;
|
|
9
9
|
|
|
10
10
|
protected operationType: OperationType = "metadata";
|
|
@@ -14,17 +14,55 @@ export class CollectionStorageSizeTool extends MongoDBToolBase {
|
|
|
14
14
|
const [{ value }] = (await provider
|
|
15
15
|
.aggregate(database, collection, [
|
|
16
16
|
{ $collStats: { storageStats: {} } },
|
|
17
|
-
{ $group: { _id: null, value: { $sum: "$storageStats.
|
|
17
|
+
{ $group: { _id: null, value: { $sum: "$storageStats.size" } } },
|
|
18
18
|
])
|
|
19
19
|
.toArray()) as [{ value: number }];
|
|
20
20
|
|
|
21
|
+
const { units, value: scaledValue } = CollectionStorageSizeTool.getStats(value);
|
|
22
|
+
|
|
21
23
|
return {
|
|
22
24
|
content: [
|
|
23
25
|
{
|
|
24
|
-
text: `The size of
|
|
26
|
+
text: `The size of "${database}.${collection}" is \`${scaledValue.toFixed(2)} ${units}\``,
|
|
25
27
|
type: "text",
|
|
26
28
|
},
|
|
27
29
|
],
|
|
28
30
|
};
|
|
29
31
|
}
|
|
32
|
+
|
|
33
|
+
protected handleError(
|
|
34
|
+
error: unknown,
|
|
35
|
+
args: ToolArgs<typeof this.argsShape>
|
|
36
|
+
): Promise<CallToolResult> | CallToolResult {
|
|
37
|
+
if (error instanceof Error && "codeName" in error && error.codeName === "NamespaceNotFound") {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
text: `The size of "${args.database}.${args.collection}" cannot be determined because the collection does not exist.`,
|
|
42
|
+
type: "text",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return super.handleError(error, args);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static getStats(value: number): { value: number; units: string } {
|
|
52
|
+
const kb = 1024;
|
|
53
|
+
const mb = kb * 1024;
|
|
54
|
+
const gb = mb * 1024;
|
|
55
|
+
|
|
56
|
+
if (value > gb) {
|
|
57
|
+
return { value: value / gb, units: "GB" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (value > mb) {
|
|
61
|
+
return { value: value / mb, units: "MB" };
|
|
62
|
+
}
|
|
63
|
+
if (value > kb) {
|
|
64
|
+
return { value: value / kb, units: "KB" };
|
|
65
|
+
}
|
|
66
|
+
return { value, units: "bytes" };
|
|
67
|
+
}
|
|
30
68
|
}
|
|
@@ -2,93 +2,95 @@ import { z } from "zod";
|
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
4
4
|
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import assert from "assert";
|
|
7
|
+
import { UserConfig } from "../../../config.js";
|
|
8
|
+
import { Telemetry } from "../../../telemetry/telemetry.js";
|
|
9
|
+
import { Session } from "../../../session.js";
|
|
10
|
+
|
|
11
|
+
const disconnectedSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
connectionString: z.string().describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format)"),
|
|
14
|
+
})
|
|
15
|
+
.describe("Options for connecting to MongoDB.");
|
|
16
|
+
|
|
17
|
+
const connectedSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
connectionString: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("MongoDB connection string to switch to (in the mongodb:// or mongodb+srv:// format)"),
|
|
23
|
+
})
|
|
24
|
+
.describe(
|
|
25
|
+
"Options for switching the current MongoDB connection. If a connection string is not provided, the connection string from the config will be used."
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const connectedName = "switch-connection" as const;
|
|
29
|
+
const disconnectedName = "connect" as const;
|
|
30
|
+
|
|
31
|
+
const connectedDescription =
|
|
32
|
+
"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.";
|
|
33
|
+
const disconnectedDescription = "Connect to a MongoDB instance";
|
|
7
34
|
|
|
8
35
|
export class ConnectTool extends MongoDBToolBase {
|
|
9
|
-
protected name =
|
|
10
|
-
protected description
|
|
36
|
+
protected name: typeof connectedName | typeof disconnectedName = disconnectedName;
|
|
37
|
+
protected description: typeof connectedDescription | typeof disconnectedDescription = disconnectedDescription;
|
|
38
|
+
|
|
39
|
+
// Here the default is empty just to trigger registration, but we're going to override it with the correct
|
|
40
|
+
// schema in the register method.
|
|
11
41
|
protected argsShape = {
|
|
12
|
-
|
|
13
|
-
.array(
|
|
14
|
-
z
|
|
15
|
-
.union([
|
|
16
|
-
z.object({
|
|
17
|
-
connectionString: z
|
|
18
|
-
.string()
|
|
19
|
-
.describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format)"),
|
|
20
|
-
}),
|
|
21
|
-
z.object({
|
|
22
|
-
clusterName: z.string().describe("MongoDB cluster name"),
|
|
23
|
-
}),
|
|
24
|
-
])
|
|
25
|
-
.optional()
|
|
26
|
-
)
|
|
27
|
-
.optional()
|
|
28
|
-
.describe(
|
|
29
|
-
"Options for connecting to MongoDB. If not provided, the connection string from the config://connection-string resource will be used. If the user hasn't specified Atlas cluster name or a connection string explicitly and the `config://connection-string` resource is present, always invoke this with no arguments."
|
|
30
|
-
),
|
|
42
|
+
connectionString: z.string().optional(),
|
|
31
43
|
};
|
|
32
44
|
|
|
33
45
|
protected operationType: OperationType = "metadata";
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{ type: "text", text: "No connection details provided." },
|
|
42
|
-
{ type: "text", text: "Please provide either a connection string or a cluster name" },
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
}
|
|
47
|
+
constructor(session: Session, config: UserConfig, telemetry: Telemetry) {
|
|
48
|
+
super(session, config, telemetry);
|
|
49
|
+
session.on("close", () => {
|
|
50
|
+
this.updateMetadata();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
type: "text",
|
|
60
|
-
text: `Connecting via cluster name not supported yet. Please provide a connection string.`,
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
54
|
+
protected async execute({ connectionString }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
55
|
+
switch (this.name) {
|
|
56
|
+
case disconnectedName:
|
|
57
|
+
assert(connectionString, "Connection string is required");
|
|
58
|
+
break;
|
|
59
|
+
case connectedName:
|
|
60
|
+
connectionString ??= this.config.connectionString;
|
|
61
|
+
assert(
|
|
62
|
+
connectionString,
|
|
63
|
+
"Cannot switch to a new connection because no connection string was provided and no default connection string is configured."
|
|
64
|
+
);
|
|
65
|
+
break;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
config.connectionString &&
|
|
76
|
-
error instanceof DriverError &&
|
|
77
|
-
config.connectionString !== connectionString
|
|
78
|
-
) {
|
|
79
|
-
return {
|
|
80
|
-
content: [
|
|
81
|
-
{
|
|
82
|
-
type: "text",
|
|
83
|
-
text:
|
|
84
|
-
`Failed to connect to MongoDB at '${connectionString}' due to error: '${error.message}.` +
|
|
85
|
-
`Your config lists a different connection string: '${config.connectionString}' - do you want to try connecting to it instead?`,
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
68
|
+
await this.connectToMongoDB(connectionString);
|
|
69
|
+
this.updateMetadata();
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: "Successfully connected to MongoDB." }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public register(server: McpServer): void {
|
|
76
|
+
super.register(server);
|
|
90
77
|
|
|
91
|
-
|
|
78
|
+
this.updateMetadata();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private updateMetadata(): void {
|
|
82
|
+
if (this.config.connectionString || this.session.serviceProvider) {
|
|
83
|
+
this.update?.({
|
|
84
|
+
name: connectedName,
|
|
85
|
+
description: connectedDescription,
|
|
86
|
+
inputSchema: connectedSchema,
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
this.update?.({
|
|
90
|
+
name: disconnectedName,
|
|
91
|
+
description: disconnectedDescription,
|
|
92
|
+
inputSchema: disconnectedSchema,
|
|
93
|
+
});
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
3
|
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
import { EJSON } from "bson";
|
|
4
5
|
|
|
5
6
|
export class DbStatsTool extends MongoDBToolBase {
|
|
6
7
|
protected name = "db-stats";
|
|
@@ -21,7 +22,11 @@ export class DbStatsTool extends MongoDBToolBase {
|
|
|
21
22
|
return {
|
|
22
23
|
content: [
|
|
23
24
|
{
|
|
24
|
-
text: `Statistics for database ${database}
|
|
25
|
+
text: `Statistics for database ${database}`,
|
|
26
|
+
type: "text",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
text: EJSON.stringify(result),
|
|
25
30
|
type: "text",
|
|
26
31
|
},
|
|
27
32
|
],
|
|
@@ -47,14 +47,24 @@ export class ExplainTool extends MongoDBToolBase {
|
|
|
47
47
|
const method = methods[0];
|
|
48
48
|
|
|
49
49
|
if (!method) {
|
|
50
|
-
throw new Error("No method provided");
|
|
50
|
+
throw new Error("No method provided. Expected one of the following: `aggregate`, `find`, or `count`");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
let result: Document;
|
|
54
54
|
switch (method.name) {
|
|
55
55
|
case "aggregate": {
|
|
56
56
|
const { pipeline } = method.arguments;
|
|
57
|
-
result = await provider
|
|
57
|
+
result = await provider
|
|
58
|
+
.aggregate(
|
|
59
|
+
database,
|
|
60
|
+
collection,
|
|
61
|
+
pipeline,
|
|
62
|
+
{},
|
|
63
|
+
{
|
|
64
|
+
writeConcern: undefined,
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
.explain(ExplainTool.defaultVerbosity);
|
|
58
68
|
break;
|
|
59
69
|
}
|
|
60
70
|
case "find": {
|
|
@@ -66,10 +76,13 @@ export class ExplainTool extends MongoDBToolBase {
|
|
|
66
76
|
}
|
|
67
77
|
case "count": {
|
|
68
78
|
const { query } = method.arguments;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
result = await provider.mongoClient.db(database).command({
|
|
80
|
+
explain: {
|
|
81
|
+
count: collection,
|
|
82
|
+
query,
|
|
83
|
+
},
|
|
84
|
+
verbosity: ExplainTool.defaultVerbosity,
|
|
85
|
+
});
|
|
73
86
|
break;
|
|
74
87
|
}
|
|
75
88
|
}
|
|
@@ -77,7 +90,7 @@ export class ExplainTool extends MongoDBToolBase {
|
|
|
77
90
|
return {
|
|
78
91
|
content: [
|
|
79
92
|
{
|
|
80
|
-
text: `Here is some information about the winning plan chosen by the query optimizer for running the given \`${method.name}\` operation in
|
|
93
|
+
text: `Here is some information about the winning plan chosen by the query optimizer for running the given \`${method.name}\` operation in "${database}.${collection}". This information can be used to understand how the query was executed and to optimize the query performance.`,
|
|
81
94
|
type: "text",
|
|
82
95
|
},
|
|
83
96
|
{
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
export class LogsTool extends MongoDBToolBase {
|
|
7
|
+
protected name = "mongodb-logs";
|
|
8
|
+
protected description = "Returns the most recent logged mongod events";
|
|
9
|
+
protected argsShape = {
|
|
10
|
+
type: z
|
|
11
|
+
.enum(["global", "startupWarnings"])
|
|
12
|
+
.optional()
|
|
13
|
+
.default("global")
|
|
14
|
+
.describe(
|
|
15
|
+
"The type of logs to return. Global returns all recent log entries, while startupWarnings returns only warnings and errors from when the process started."
|
|
16
|
+
),
|
|
17
|
+
limit: z
|
|
18
|
+
.number()
|
|
19
|
+
.int()
|
|
20
|
+
.max(1024)
|
|
21
|
+
.min(1)
|
|
22
|
+
.optional()
|
|
23
|
+
.default(50)
|
|
24
|
+
.describe("The maximum number of log entries to return."),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
protected operationType: OperationType = "metadata";
|
|
28
|
+
|
|
29
|
+
protected async execute({ type, limit }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
30
|
+
const provider = await this.ensureConnected();
|
|
31
|
+
|
|
32
|
+
const result = await provider.runCommandWithCheck("admin", {
|
|
33
|
+
getLog: type,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const logs = (result.log as string[]).slice(0, limit);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
text: `Found: ${result.totalLinesWritten} messages`,
|
|
42
|
+
type: "text",
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
...logs.map(
|
|
46
|
+
(log) =>
|
|
47
|
+
({
|
|
48
|
+
text: log,
|
|
49
|
+
type: "text",
|
|
50
|
+
}) as const
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ToolBase, ToolCategory } from "../tool.js";
|
|
3
|
-
import { Session } from "../../session.js";
|
|
2
|
+
import { ToolArgs, ToolBase, ToolCategory } from "../tool.js";
|
|
4
3
|
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
|
|
5
4
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
6
5
|
import { ErrorCodes, MongoDBError } from "../../errors.js";
|
|
7
|
-
import
|
|
6
|
+
import logger, { LogId } from "../../logger.js";
|
|
8
7
|
|
|
9
8
|
export const DbOperationArgs = {
|
|
10
9
|
database: z.string().describe("Database name"),
|
|
@@ -12,15 +11,20 @@ export const DbOperationArgs = {
|
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
export abstract class MongoDBToolBase extends ToolBase {
|
|
15
|
-
constructor(session: Session) {
|
|
16
|
-
super(session);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
protected category: ToolCategory = "mongodb";
|
|
20
15
|
|
|
21
16
|
protected async ensureConnected(): Promise<NodeDriverServiceProvider> {
|
|
22
|
-
if (!this.session.serviceProvider && config.connectionString) {
|
|
23
|
-
|
|
17
|
+
if (!this.session.serviceProvider && this.config.connectionString) {
|
|
18
|
+
try {
|
|
19
|
+
await this.connectToMongoDB(this.config.connectionString);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error(
|
|
22
|
+
LogId.mongodbConnectFailure,
|
|
23
|
+
"mongodbTool",
|
|
24
|
+
`Failed to connect to MongoDB instance using the connection string from the config: ${error as string}`
|
|
25
|
+
);
|
|
26
|
+
throw new MongoDBError(ErrorCodes.MisconfiguredConnectionString, "Not connected to MongoDB.");
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
if (!this.session.serviceProvider) {
|
|
@@ -30,40 +34,43 @@ export abstract class MongoDBToolBase extends ToolBase {
|
|
|
30
34
|
return this.session.serviceProvider;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
protected handleError(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
protected handleError(
|
|
38
|
+
error: unknown,
|
|
39
|
+
args: ToolArgs<typeof this.argsShape>
|
|
40
|
+
): Promise<CallToolResult> | CallToolResult {
|
|
41
|
+
if (error instanceof MongoDBError) {
|
|
42
|
+
switch (error.code) {
|
|
43
|
+
case ErrorCodes.NotConnectedToMongoDB:
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: "You need to connect to a MongoDB instance before you can access its data.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: "Please use the 'connect' or 'switch-connection' tool to connect to a MongoDB instance.",
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
case ErrorCodes.MisconfiguredConnectionString:
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: "The configured connection string is not valid. Please check the connection string and confirm it points to a valid MongoDB instance. Alternatively, use the 'switch-connection' tool to connect to a different instance.",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
return super.handleError(error);
|
|
70
|
+
return super.handleError(error, args);
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
protected
|
|
54
|
-
|
|
55
|
-
productDocsLink: "https://docs.mongodb.com/todo-mcp",
|
|
56
|
-
productName: "MongoDB MCP",
|
|
57
|
-
readConcern: {
|
|
58
|
-
level: config.connectOptions.readConcern,
|
|
59
|
-
},
|
|
60
|
-
readPreference: config.connectOptions.readPreference,
|
|
61
|
-
writeConcern: {
|
|
62
|
-
w: config.connectOptions.writeConcern,
|
|
63
|
-
},
|
|
64
|
-
timeoutMS: config.connectOptions.timeoutMS,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
this.session.serviceProvider = provider;
|
|
73
|
+
protected connectToMongoDB(connectionString: string): Promise<void> {
|
|
74
|
+
return this.session.connectToMongoDB(connectionString, this.config.connectOptions);
|
|
68
75
|
}
|
|
69
76
|
}
|
|
@@ -2,10 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
4
4
|
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { EJSON } from "bson";
|
|
5
6
|
|
|
6
7
|
export const AggregateArgs = {
|
|
7
|
-
pipeline: z.array(z.
|
|
8
|
-
limit: z.number().optional().default(10).describe("The maximum number of documents to return"),
|
|
8
|
+
pipeline: z.array(z.record(z.string(), z.unknown())).describe("An array of aggregation stages to execute"),
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export class AggregateTool extends MongoDBToolBase {
|
|
@@ -27,12 +27,12 @@ export class AggregateTool extends MongoDBToolBase {
|
|
|
27
27
|
|
|
28
28
|
const content: Array<{ text: string; type: "text" }> = [
|
|
29
29
|
{
|
|
30
|
-
text: `Found ${documents.length} documents in the collection
|
|
30
|
+
text: `Found ${documents.length} documents in the collection "${collection}":`,
|
|
31
31
|
type: "text",
|
|
32
32
|
},
|
|
33
33
|
...documents.map((doc) => {
|
|
34
34
|
return {
|
|
35
|
-
text:
|
|
35
|
+
text: EJSON.stringify(doc),
|
|
36
36
|
type: "text",
|
|
37
37
|
} as { text: string; type: "text" };
|
|
38
38
|
}),
|
|
@@ -13,12 +13,36 @@ export class CollectionIndexesTool extends MongoDBToolBase {
|
|
|
13
13
|
const indexes = await provider.getIndexes(database, collection);
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
|
-
content:
|
|
17
|
-
|
|
18
|
-
text: `
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
text: `Found ${indexes.length} indexes in the collection "${collection}":`,
|
|
19
19
|
type: "text",
|
|
20
|
-
}
|
|
21
|
-
|
|
20
|
+
},
|
|
21
|
+
...(indexes.map((indexDefinition) => {
|
|
22
|
+
return {
|
|
23
|
+
text: `Name "${indexDefinition.name}", definition: ${JSON.stringify(indexDefinition.key)}`,
|
|
24
|
+
type: "text",
|
|
25
|
+
};
|
|
26
|
+
}) as { text: string; type: "text" }[]),
|
|
27
|
+
],
|
|
22
28
|
};
|
|
23
29
|
}
|
|
30
|
+
|
|
31
|
+
protected handleError(
|
|
32
|
+
error: unknown,
|
|
33
|
+
args: ToolArgs<typeof this.argsShape>
|
|
34
|
+
): Promise<CallToolResult> | CallToolResult {
|
|
35
|
+
if (error instanceof Error && "codeName" in error && error.codeName === "NamespaceNotFound") {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
text: `The indexes for "${args.database}.${args.collection}" cannot be determined because the collection does not exist.`,
|
|
40
|
+
type: "text",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return super.handleError(error, args);
|
|
47
|
+
}
|
|
24
48
|
}
|
|
@@ -5,8 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
|
|
6
6
|
export const CountArgs = {
|
|
7
7
|
query: z
|
|
8
|
-
.
|
|
9
|
-
.passthrough()
|
|
8
|
+
.record(z.string(), z.unknown())
|
|
10
9
|
.optional()
|
|
11
10
|
.describe(
|
|
12
11
|
"The query filter to count documents. Matches the syntax of the filter argument of db.collection.count()"
|
|
@@ -3,16 +3,15 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
|
3
3
|
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
4
4
|
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
5
|
import { SortDirection } from "mongodb";
|
|
6
|
+
import { EJSON } from "bson";
|
|
6
7
|
|
|
7
8
|
export const FindArgs = {
|
|
8
9
|
filter: z
|
|
9
|
-
.
|
|
10
|
-
.passthrough()
|
|
10
|
+
.record(z.string(), z.unknown())
|
|
11
11
|
.optional()
|
|
12
12
|
.describe("The query filter, matching the syntax of the query argument of db.collection.find()"),
|
|
13
13
|
projection: z
|
|
14
|
-
.
|
|
15
|
-
.passthrough()
|
|
14
|
+
.record(z.string(), z.unknown())
|
|
16
15
|
.optional()
|
|
17
16
|
.describe("The projection, matching the syntax of the projection argument of db.collection.find()"),
|
|
18
17
|
limit: z.number().optional().default(10).describe("The maximum number of documents to return"),
|
|
@@ -44,12 +43,12 @@ export class FindTool extends MongoDBToolBase {
|
|
|
44
43
|
|
|
45
44
|
const content: Array<{ text: string; type: "text" }> = [
|
|
46
45
|
{
|
|
47
|
-
text: `Found ${documents.length} documents in the collection
|
|
46
|
+
text: `Found ${documents.length} documents in the collection "${collection}":`,
|
|
48
47
|
type: "text",
|
|
49
48
|
},
|
|
50
49
|
...documents.map((doc) => {
|
|
51
50
|
return {
|
|
52
|
-
text:
|
|
51
|
+
text: EJSON.stringify(doc),
|
|
53
52
|
type: "text",
|
|
54
53
|
} as { text: string; type: "text" };
|
|
55
54
|
}),
|