mongodb-mcp-server 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/code_health.yaml +65 -0
- package/.github/workflows/prepare_release.yaml +45 -0
- package/.github/workflows/publish.yaml +70 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +37 -0
- package/.vscode/launch.json +17 -0
- package/CONTRIBUTING.md +185 -0
- package/LICENSE +202 -0
- package/README.md +234 -0
- package/dist/common/atlas/apiClient.js +147 -0
- package/dist/common/atlas/apiClient.js.map +1 -0
- package/dist/common/atlas/apiClientError.js +17 -0
- package/dist/common/atlas/apiClientError.js.map +1 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.js +97 -0
- package/dist/logger.js.map +1 -0
- package/dist/server.js +45 -0
- package/dist/server.js.map +1 -0
- package/dist/session.js +30 -0
- package/dist/session.js.map +1 -0
- package/dist/tools/atlas/atlasTool.js +9 -0
- package/dist/tools/atlas/atlasTool.js.map +1 -0
- package/dist/tools/atlas/createAccessList.js +64 -0
- package/dist/tools/atlas/createAccessList.js.map +1 -0
- package/dist/tools/atlas/createDBUser.js +58 -0
- package/dist/tools/atlas/createDBUser.js.map +1 -0
- package/dist/tools/atlas/createFreeCluster.js +51 -0
- package/dist/tools/atlas/createFreeCluster.js.map +1 -0
- package/dist/tools/atlas/createProject.js +53 -0
- package/dist/tools/atlas/createProject.js.map +1 -0
- package/dist/tools/atlas/inspectAccessList.js +41 -0
- package/dist/tools/atlas/inspectAccessList.js.map +1 -0
- package/dist/tools/atlas/inspectCluster.js +42 -0
- package/dist/tools/atlas/inspectCluster.js.map +1 -0
- package/dist/tools/atlas/listClusters.js +97 -0
- package/dist/tools/atlas/listClusters.js.map +1 -0
- package/dist/tools/atlas/listDBUsers.js +52 -0
- package/dist/tools/atlas/listDBUsers.js.map +1 -0
- package/dist/tools/atlas/listOrgs.js +30 -0
- package/dist/tools/atlas/listOrgs.js.map +1 -0
- package/dist/tools/atlas/listProjects.js +42 -0
- package/dist/tools/atlas/listProjects.js.map +1 -0
- package/dist/tools/atlas/tools.js +23 -0
- package/dist/tools/atlas/tools.js.map +1 -0
- package/dist/tools/mongodb/create/createCollection.js +23 -0
- package/dist/tools/mongodb/create/createCollection.js.map +1 -0
- package/dist/tools/mongodb/create/createIndex.js +33 -0
- package/dist/tools/mongodb/create/createIndex.js.map +1 -0
- package/dist/tools/mongodb/create/insertMany.js +33 -0
- package/dist/tools/mongodb/create/insertMany.js.map +1 -0
- package/dist/tools/mongodb/delete/deleteMany.js +31 -0
- package/dist/tools/mongodb/delete/deleteMany.js.map +1 -0
- package/dist/tools/mongodb/delete/dropCollection.js +25 -0
- package/dist/tools/mongodb/delete/dropCollection.js.map +1 -0
- package/dist/tools/mongodb/delete/dropDatabase.js +25 -0
- package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -0
- package/dist/tools/mongodb/metadata/collectionSchema.js +38 -0
- package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -0
- package/dist/tools/mongodb/metadata/collectionStorageSize.js +28 -0
- package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -0
- package/dist/tools/mongodb/metadata/connect.js +86 -0
- package/dist/tools/mongodb/metadata/connect.js.map +1 -0
- package/dist/tools/mongodb/metadata/dbStats.js +28 -0
- package/dist/tools/mongodb/metadata/dbStats.js.map +1 -0
- package/dist/tools/mongodb/metadata/explain.js +77 -0
- package/dist/tools/mongodb/metadata/explain.js.map +1 -0
- package/dist/tools/mongodb/metadata/listCollections.js +35 -0
- package/dist/tools/mongodb/metadata/listCollections.js.map +1 -0
- package/dist/tools/mongodb/metadata/listDatabases.js +23 -0
- package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -0
- package/dist/tools/mongodb/mongodbTool.js +58 -0
- package/dist/tools/mongodb/mongodbTool.js.map +1 -0
- package/dist/tools/mongodb/read/aggregate.js +38 -0
- package/dist/tools/mongodb/read/aggregate.js.map +1 -0
- package/dist/tools/mongodb/read/collectionIndexes.js +23 -0
- package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -0
- package/dist/tools/mongodb/read/count.js +34 -0
- package/dist/tools/mongodb/read/count.js.map +1 -0
- package/dist/tools/mongodb/read/find.js +51 -0
- package/dist/tools/mongodb/read/find.js.map +1 -0
- package/dist/tools/mongodb/tools.js +41 -0
- package/dist/tools/mongodb/tools.js.map +1 -0
- package/dist/tools/mongodb/update/renameCollection.js +31 -0
- package/dist/tools/mongodb/update/renameCollection.js.map +1 -0
- package/dist/tools/mongodb/update/updateMany.js +56 -0
- package/dist/tools/mongodb/update/updateMany.js.map +1 -0
- package/dist/tools/tool.js +56 -0
- package/dist/tools/tool.js.map +1 -0
- package/eslint.config.js +35 -0
- package/jest.config.js +22 -0
- package/package.json +76 -0
- package/scripts/apply.ts +129 -0
- package/scripts/filter.ts +67 -0
- package/scripts/generate.sh +11 -0
- package/src/common/atlas/apiClient.ts +202 -0
- package/src/common/atlas/apiClientError.ts +21 -0
- package/src/common/atlas/openapi.d.ts +5849 -0
- package/src/config.ts +124 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +30 -0
- package/src/logger.ts +117 -0
- package/src/server.ts +64 -0
- package/src/session.ts +37 -0
- package/src/tools/atlas/atlasTool.ts +10 -0
- package/src/tools/atlas/createAccessList.ts +78 -0
- package/src/tools/atlas/createDBUser.ts +70 -0
- package/src/tools/atlas/createFreeCluster.ts +55 -0
- package/src/tools/atlas/createProject.ts +63 -0
- package/src/tools/atlas/inspectAccessList.ts +44 -0
- package/src/tools/atlas/inspectCluster.ts +47 -0
- package/src/tools/atlas/listClusters.ts +104 -0
- package/src/tools/atlas/listDBUsers.ts +62 -0
- package/src/tools/atlas/listOrgs.ts +34 -0
- package/src/tools/atlas/listProjects.ts +46 -0
- package/src/tools/atlas/tools.ts +23 -0
- package/src/tools/mongodb/create/createCollection.ts +26 -0
- package/src/tools/mongodb/create/createIndex.ts +41 -0
- package/src/tools/mongodb/create/insertMany.ts +40 -0
- package/src/tools/mongodb/delete/deleteMany.ts +38 -0
- package/src/tools/mongodb/delete/dropCollection.ts +27 -0
- package/src/tools/mongodb/delete/dropDatabase.ts +26 -0
- package/src/tools/mongodb/metadata/collectionSchema.ts +41 -0
- package/src/tools/mongodb/metadata/collectionStorageSize.ts +30 -0
- package/src/tools/mongodb/metadata/connect.ts +94 -0
- package/src/tools/mongodb/metadata/dbStats.ts +30 -0
- package/src/tools/mongodb/metadata/explain.ts +90 -0
- package/src/tools/mongodb/metadata/listCollections.ts +38 -0
- package/src/tools/mongodb/metadata/listDatabases.ts +26 -0
- package/src/tools/mongodb/mongodbTool.ts +69 -0
- package/src/tools/mongodb/read/aggregate.ts +45 -0
- package/src/tools/mongodb/read/collectionIndexes.ts +24 -0
- package/src/tools/mongodb/read/count.ts +39 -0
- package/src/tools/mongodb/read/find.ts +62 -0
- package/src/tools/mongodb/tools.ts +41 -0
- package/src/tools/mongodb/update/renameCollection.ts +37 -0
- package/src/tools/mongodb/update/updateMany.ts +65 -0
- package/src/tools/tool.ts +90 -0
- package/src/types/mongodb-redact.d.ts +4 -0
- package/tests/integration/helpers.ts +241 -0
- package/tests/integration/inMemoryTransport.ts +58 -0
- package/tests/integration/server.test.ts +35 -0
- package/tests/integration/tools/atlas/accessLists.test.ts +100 -0
- package/tests/integration/tools/atlas/atlasHelpers.ts +110 -0
- package/tests/integration/tools/atlas/clusters.test.ts +122 -0
- package/tests/integration/tools/atlas/dbUsers.test.ts +80 -0
- package/tests/integration/tools/atlas/orgs.test.ts +24 -0
- package/tests/integration/tools/atlas/projects.test.ts +80 -0
- package/tests/integration/tools/mongodb/create/createCollection.test.ts +138 -0
- package/tests/integration/tools/mongodb/create/createIndex.test.ts +249 -0
- package/tests/integration/tools/mongodb/create/insertMany.test.ts +141 -0
- package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +191 -0
- package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +118 -0
- package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +114 -0
- package/tests/integration/tools/mongodb/metadata/connect.test.ts +137 -0
- package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +104 -0
- package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +67 -0
- package/tests/integration/tools/mongodb/read/count.test.ts +138 -0
- package/tsconfig.jest.json +10 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import config from "../../../config.js";
|
|
6
|
+
import { MongoError as DriverError } from "mongodb";
|
|
7
|
+
|
|
8
|
+
export class ConnectTool extends MongoDBToolBase {
|
|
9
|
+
protected name = "connect";
|
|
10
|
+
protected description = "Connect to a MongoDB instance";
|
|
11
|
+
protected argsShape = {
|
|
12
|
+
options: z
|
|
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
|
+
),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
protected operationType: OperationType = "metadata";
|
|
34
|
+
|
|
35
|
+
protected async execute({ options: optionsArr }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
36
|
+
const options = optionsArr?.[0];
|
|
37
|
+
let connectionString: string;
|
|
38
|
+
if (!options && !config.connectionString) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
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
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!options) {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
49
|
+
connectionString = config.connectionString!;
|
|
50
|
+
} else if ("connectionString" in options) {
|
|
51
|
+
connectionString = options.connectionString;
|
|
52
|
+
} else {
|
|
53
|
+
// TODO: https://github.com/mongodb-js/mongodb-mcp-server/issues/19
|
|
54
|
+
// We don't support connecting via cluster name since we'd need to obtain the user credentials
|
|
55
|
+
// and fill in the connection string.
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Connecting via cluster name not supported yet. Please provide a connection string.`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await this.connectToMongoDB(connectionString);
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: `Successfully connected to ${connectionString}.` }],
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Sometimes the model will supply an incorrect connection string. If the user has configured
|
|
73
|
+
// a different one as environment variable or a cli argument, suggest using that one instead.
|
|
74
|
+
if (
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
|
|
5
|
+
export class DbStatsTool extends MongoDBToolBase {
|
|
6
|
+
protected name = "db-stats";
|
|
7
|
+
protected description = "Returns statistics that reflect the use state of a single database";
|
|
8
|
+
protected argsShape = {
|
|
9
|
+
database: DbOperationArgs.database,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
protected operationType: OperationType = "metadata";
|
|
13
|
+
|
|
14
|
+
protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
15
|
+
const provider = await this.ensureConnected();
|
|
16
|
+
const result = await provider.runCommandWithCheck(database, {
|
|
17
|
+
dbStats: 1,
|
|
18
|
+
scale: 1,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
text: `Statistics for database ${database}: ${JSON.stringify(result)}`,
|
|
25
|
+
type: "text",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ExplainVerbosity, Document } from "mongodb";
|
|
6
|
+
import { AggregateArgs } from "../read/aggregate.js";
|
|
7
|
+
import { FindArgs } from "../read/find.js";
|
|
8
|
+
import { CountArgs } from "../read/count.js";
|
|
9
|
+
|
|
10
|
+
export class ExplainTool extends MongoDBToolBase {
|
|
11
|
+
protected name = "explain";
|
|
12
|
+
protected description =
|
|
13
|
+
"Returns statistics describing the execution of the winning plan chosen by the query optimizer for the evaluated method";
|
|
14
|
+
|
|
15
|
+
protected argsShape = {
|
|
16
|
+
...DbOperationArgs,
|
|
17
|
+
method: z
|
|
18
|
+
.array(
|
|
19
|
+
z.union([
|
|
20
|
+
z.object({
|
|
21
|
+
name: z.literal("aggregate"),
|
|
22
|
+
arguments: z.object(AggregateArgs),
|
|
23
|
+
}),
|
|
24
|
+
z.object({
|
|
25
|
+
name: z.literal("find"),
|
|
26
|
+
arguments: z.object(FindArgs),
|
|
27
|
+
}),
|
|
28
|
+
z.object({
|
|
29
|
+
name: z.literal("count"),
|
|
30
|
+
arguments: z.object(CountArgs),
|
|
31
|
+
}),
|
|
32
|
+
])
|
|
33
|
+
)
|
|
34
|
+
.describe("The method and its arguments to run"),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
protected operationType: OperationType = "metadata";
|
|
38
|
+
|
|
39
|
+
static readonly defaultVerbosity = ExplainVerbosity.queryPlanner;
|
|
40
|
+
|
|
41
|
+
protected async execute({
|
|
42
|
+
database,
|
|
43
|
+
collection,
|
|
44
|
+
method: methods,
|
|
45
|
+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
46
|
+
const provider = await this.ensureConnected();
|
|
47
|
+
const method = methods[0];
|
|
48
|
+
|
|
49
|
+
if (!method) {
|
|
50
|
+
throw new Error("No method provided");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let result: Document;
|
|
54
|
+
switch (method.name) {
|
|
55
|
+
case "aggregate": {
|
|
56
|
+
const { pipeline } = method.arguments;
|
|
57
|
+
result = await provider.aggregate(database, collection, pipeline).explain(ExplainTool.defaultVerbosity);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "find": {
|
|
61
|
+
const { filter, ...rest } = method.arguments;
|
|
62
|
+
result = await provider
|
|
63
|
+
.find(database, collection, filter as Document, { ...rest })
|
|
64
|
+
.explain(ExplainTool.defaultVerbosity);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "count": {
|
|
68
|
+
const { query } = method.arguments;
|
|
69
|
+
// This helper doesn't have explain() command but does have the argument explain
|
|
70
|
+
result = (await provider.count(database, collection, query, {
|
|
71
|
+
explain: ExplainTool.defaultVerbosity,
|
|
72
|
+
})) as unknown as Document;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
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
|
+
type: "text",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
text: JSON.stringify(result),
|
|
85
|
+
type: "text",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
|
|
5
|
+
export class ListCollectionsTool extends MongoDBToolBase {
|
|
6
|
+
protected name = "list-collections";
|
|
7
|
+
protected description = "List all collections for a given database";
|
|
8
|
+
protected argsShape = {
|
|
9
|
+
database: DbOperationArgs.database,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
protected operationType: OperationType = "metadata";
|
|
13
|
+
|
|
14
|
+
protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
15
|
+
const provider = await this.ensureConnected();
|
|
16
|
+
const collections = await provider.listCollections(database);
|
|
17
|
+
|
|
18
|
+
if (collections.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: `No collections found for database "${database}". To create a collection, use the "create-collection" tool.`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
content: collections.map((collection) => {
|
|
31
|
+
return {
|
|
32
|
+
text: `Name: "${collection.name}"`,
|
|
33
|
+
type: "text",
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import * as bson from "bson";
|
|
4
|
+
import { OperationType } from "../../tool.js";
|
|
5
|
+
|
|
6
|
+
export class ListDatabasesTool extends MongoDBToolBase {
|
|
7
|
+
protected name = "list-databases";
|
|
8
|
+
protected description = "List all databases for a MongoDB connection";
|
|
9
|
+
protected argsShape = {};
|
|
10
|
+
|
|
11
|
+
protected operationType: OperationType = "metadata";
|
|
12
|
+
|
|
13
|
+
protected async execute(): Promise<CallToolResult> {
|
|
14
|
+
const provider = await this.ensureConnected();
|
|
15
|
+
const dbs = (await provider.listDatabases("")).databases as { name: string; sizeOnDisk: bson.Long }[];
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
content: dbs.map((db) => {
|
|
19
|
+
return {
|
|
20
|
+
text: `Name: ${db.name}, Size: ${db.sizeOnDisk.toString()} bytes`,
|
|
21
|
+
type: "text",
|
|
22
|
+
};
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ToolBase, ToolCategory } from "../tool.js";
|
|
3
|
+
import { Session } from "../../session.js";
|
|
4
|
+
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
|
|
5
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { ErrorCodes, MongoDBError } from "../../errors.js";
|
|
7
|
+
import config from "../../config.js";
|
|
8
|
+
|
|
9
|
+
export const DbOperationArgs = {
|
|
10
|
+
database: z.string().describe("Database name"),
|
|
11
|
+
collection: z.string().describe("Collection name"),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export abstract class MongoDBToolBase extends ToolBase {
|
|
15
|
+
constructor(session: Session) {
|
|
16
|
+
super(session);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected category: ToolCategory = "mongodb";
|
|
20
|
+
|
|
21
|
+
protected async ensureConnected(): Promise<NodeDriverServiceProvider> {
|
|
22
|
+
if (!this.session.serviceProvider && config.connectionString) {
|
|
23
|
+
await this.connectToMongoDB(config.connectionString);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!this.session.serviceProvider) {
|
|
27
|
+
throw new MongoDBError(ErrorCodes.NotConnectedToMongoDB, "Not connected to MongoDB");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return this.session.serviceProvider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected handleError(error: unknown): Promise<CallToolResult> | CallToolResult {
|
|
34
|
+
if (error instanceof MongoDBError && error.code === ErrorCodes.NotConnectedToMongoDB) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: "You need to connect to a MongoDB instance before you can access its data.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: "Please use the 'connect' tool to connect to a MongoDB instance.",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return super.handleError(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected async connectToMongoDB(connectionString: string): Promise<void> {
|
|
54
|
+
const provider = await NodeDriverServiceProvider.connect(connectionString, {
|
|
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;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
|
|
6
|
+
export const AggregateArgs = {
|
|
7
|
+
pipeline: z.array(z.object({}).passthrough()).describe("An array of aggregation stages to execute"),
|
|
8
|
+
limit: z.number().optional().default(10).describe("The maximum number of documents to return"),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class AggregateTool extends MongoDBToolBase {
|
|
12
|
+
protected name = "aggregate";
|
|
13
|
+
protected description = "Run an aggregation against a MongoDB collection";
|
|
14
|
+
protected argsShape = {
|
|
15
|
+
...DbOperationArgs,
|
|
16
|
+
...AggregateArgs,
|
|
17
|
+
};
|
|
18
|
+
protected operationType: OperationType = "read";
|
|
19
|
+
|
|
20
|
+
protected async execute({
|
|
21
|
+
database,
|
|
22
|
+
collection,
|
|
23
|
+
pipeline,
|
|
24
|
+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
25
|
+
const provider = await this.ensureConnected();
|
|
26
|
+
const documents = await provider.aggregate(database, collection, pipeline).toArray();
|
|
27
|
+
|
|
28
|
+
const content: Array<{ text: string; type: "text" }> = [
|
|
29
|
+
{
|
|
30
|
+
text: `Found ${documents.length} documents in the collection \`${collection}\`:`,
|
|
31
|
+
type: "text",
|
|
32
|
+
},
|
|
33
|
+
...documents.map((doc) => {
|
|
34
|
+
return {
|
|
35
|
+
text: JSON.stringify(doc),
|
|
36
|
+
type: "text",
|
|
37
|
+
} as { text: string; type: "text" };
|
|
38
|
+
}),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
|
|
5
|
+
export class CollectionIndexesTool extends MongoDBToolBase {
|
|
6
|
+
protected name = "collection-indexes";
|
|
7
|
+
protected description = "Describe the indexes for a collection";
|
|
8
|
+
protected argsShape = DbOperationArgs;
|
|
9
|
+
protected operationType: OperationType = "read";
|
|
10
|
+
|
|
11
|
+
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
|
|
12
|
+
const provider = await this.ensureConnected();
|
|
13
|
+
const indexes = await provider.getIndexes(database, collection);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
content: indexes.map((indexDefinition) => {
|
|
17
|
+
return {
|
|
18
|
+
text: `Field: ${indexDefinition.name}: ${JSON.stringify(indexDefinition.key)}`,
|
|
19
|
+
type: "text",
|
|
20
|
+
};
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
3
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
export const CountArgs = {
|
|
7
|
+
query: z
|
|
8
|
+
.object({})
|
|
9
|
+
.passthrough()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe(
|
|
12
|
+
"The query filter to count documents. Matches the syntax of the filter argument of db.collection.count()"
|
|
13
|
+
),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class CountTool extends MongoDBToolBase {
|
|
17
|
+
protected name = "count";
|
|
18
|
+
protected description = "Gets the number of documents in a MongoDB collection";
|
|
19
|
+
protected argsShape = {
|
|
20
|
+
...DbOperationArgs,
|
|
21
|
+
...CountArgs,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
protected operationType: OperationType = "read";
|
|
25
|
+
|
|
26
|
+
protected async execute({ database, collection, query }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
27
|
+
const provider = await this.ensureConnected();
|
|
28
|
+
const count = await provider.count(database, collection, query);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
text: `Found ${count} documents in the collection "${collection}"`,
|
|
34
|
+
type: "text",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
import { SortDirection } from "mongodb";
|
|
6
|
+
|
|
7
|
+
export const FindArgs = {
|
|
8
|
+
filter: z
|
|
9
|
+
.object({})
|
|
10
|
+
.passthrough()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("The query filter, matching the syntax of the query argument of db.collection.find()"),
|
|
13
|
+
projection: z
|
|
14
|
+
.object({})
|
|
15
|
+
.passthrough()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("The projection, matching the syntax of the projection argument of db.collection.find()"),
|
|
18
|
+
limit: z.number().optional().default(10).describe("The maximum number of documents to return"),
|
|
19
|
+
sort: z
|
|
20
|
+
.record(z.string(), z.custom<SortDirection>())
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("A document, describing the sort order, matching the syntax of the sort argument of cursor.sort()"),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class FindTool extends MongoDBToolBase {
|
|
26
|
+
protected name = "find";
|
|
27
|
+
protected description = "Run a find query against a MongoDB collection";
|
|
28
|
+
protected argsShape = {
|
|
29
|
+
...DbOperationArgs,
|
|
30
|
+
...FindArgs,
|
|
31
|
+
};
|
|
32
|
+
protected operationType: OperationType = "read";
|
|
33
|
+
|
|
34
|
+
protected async execute({
|
|
35
|
+
database,
|
|
36
|
+
collection,
|
|
37
|
+
filter,
|
|
38
|
+
projection,
|
|
39
|
+
limit,
|
|
40
|
+
sort,
|
|
41
|
+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
42
|
+
const provider = await this.ensureConnected();
|
|
43
|
+
const documents = await provider.find(database, collection, filter, { projection, limit, sort }).toArray();
|
|
44
|
+
|
|
45
|
+
const content: Array<{ text: string; type: "text" }> = [
|
|
46
|
+
{
|
|
47
|
+
text: `Found ${documents.length} documents in the collection \`${collection}\`:`,
|
|
48
|
+
type: "text",
|
|
49
|
+
},
|
|
50
|
+
...documents.map((doc) => {
|
|
51
|
+
return {
|
|
52
|
+
text: JSON.stringify(doc),
|
|
53
|
+
type: "text",
|
|
54
|
+
} as { text: string; type: "text" };
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
content,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ConnectTool } from "./metadata/connect.js";
|
|
2
|
+
import { ListCollectionsTool } from "./metadata/listCollections.js";
|
|
3
|
+
import { CollectionIndexesTool } from "./read/collectionIndexes.js";
|
|
4
|
+
import { ListDatabasesTool } from "./metadata/listDatabases.js";
|
|
5
|
+
import { CreateIndexTool } from "./create/createIndex.js";
|
|
6
|
+
import { CollectionSchemaTool } from "./metadata/collectionSchema.js";
|
|
7
|
+
import { FindTool } from "./read/find.js";
|
|
8
|
+
import { InsertManyTool } from "./create/insertMany.js";
|
|
9
|
+
import { DeleteManyTool } from "./delete/deleteMany.js";
|
|
10
|
+
import { CollectionStorageSizeTool } from "./metadata/collectionStorageSize.js";
|
|
11
|
+
import { CountTool } from "./read/count.js";
|
|
12
|
+
import { DbStatsTool } from "./metadata/dbStats.js";
|
|
13
|
+
import { AggregateTool } from "./read/aggregate.js";
|
|
14
|
+
import { UpdateManyTool } from "./update/updateMany.js";
|
|
15
|
+
import { RenameCollectionTool } from "./update/renameCollection.js";
|
|
16
|
+
import { DropDatabaseTool } from "./delete/dropDatabase.js";
|
|
17
|
+
import { DropCollectionTool } from "./delete/dropCollection.js";
|
|
18
|
+
import { ExplainTool } from "./metadata/explain.js";
|
|
19
|
+
import { CreateCollectionTool } from "./create/createCollection.js";
|
|
20
|
+
|
|
21
|
+
export const MongoDbTools = [
|
|
22
|
+
ConnectTool,
|
|
23
|
+
ListCollectionsTool,
|
|
24
|
+
ListDatabasesTool,
|
|
25
|
+
CollectionIndexesTool,
|
|
26
|
+
CreateIndexTool,
|
|
27
|
+
CollectionSchemaTool,
|
|
28
|
+
FindTool,
|
|
29
|
+
InsertManyTool,
|
|
30
|
+
DeleteManyTool,
|
|
31
|
+
CollectionStorageSizeTool,
|
|
32
|
+
CountTool,
|
|
33
|
+
DbStatsTool,
|
|
34
|
+
AggregateTool,
|
|
35
|
+
UpdateManyTool,
|
|
36
|
+
RenameCollectionTool,
|
|
37
|
+
DropDatabaseTool,
|
|
38
|
+
DropCollectionTool,
|
|
39
|
+
ExplainTool,
|
|
40
|
+
CreateCollectionTool,
|
|
41
|
+
];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
|
|
6
|
+
export class RenameCollectionTool extends MongoDBToolBase {
|
|
7
|
+
protected name = "rename-collection";
|
|
8
|
+
protected description = "Renames a collection in a MongoDB database";
|
|
9
|
+
protected argsShape = {
|
|
10
|
+
collection: z.string().describe("Collection name"),
|
|
11
|
+
database: z.string().describe("Database name"),
|
|
12
|
+
newName: z.string().describe("The new name for the collection"),
|
|
13
|
+
dropTarget: z.boolean().optional().default(false).describe("If true, drops the target collection if it exists"),
|
|
14
|
+
};
|
|
15
|
+
protected operationType: OperationType = "update";
|
|
16
|
+
|
|
17
|
+
protected async execute({
|
|
18
|
+
database,
|
|
19
|
+
collection,
|
|
20
|
+
newName,
|
|
21
|
+
dropTarget,
|
|
22
|
+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
23
|
+
const provider = await this.ensureConnected();
|
|
24
|
+
const result = await provider.renameCollection(database, collection, newName, {
|
|
25
|
+
dropTarget,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
text: `Collection \`${collection}\` renamed to \`${result.collectionName}\` in database \`${database}\`.`,
|
|
32
|
+
type: "text",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { MongoDBToolBase } from "../mongodbTool.js";
|
|
4
|
+
import { ToolArgs, OperationType } from "../../tool.js";
|
|
5
|
+
|
|
6
|
+
export class UpdateManyTool extends MongoDBToolBase {
|
|
7
|
+
protected name = "update-many";
|
|
8
|
+
protected description = "Updates all documents that match the specified filter for a collection";
|
|
9
|
+
protected argsShape = {
|
|
10
|
+
collection: z.string().describe("Collection name"),
|
|
11
|
+
database: z.string().describe("Database name"),
|
|
12
|
+
filter: z
|
|
13
|
+
.object({})
|
|
14
|
+
.passthrough()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe(
|
|
17
|
+
"The selection criteria for the update, matching the syntax of the filter argument of db.collection.updateOne()"
|
|
18
|
+
),
|
|
19
|
+
update: z
|
|
20
|
+
.object({})
|
|
21
|
+
.passthrough()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("An update document describing the modifications to apply using update operator expressions"),
|
|
24
|
+
upsert: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Controls whether to insert a new document if no documents match the filter"),
|
|
28
|
+
};
|
|
29
|
+
protected operationType: OperationType = "update";
|
|
30
|
+
|
|
31
|
+
protected async execute({
|
|
32
|
+
database,
|
|
33
|
+
collection,
|
|
34
|
+
filter,
|
|
35
|
+
update,
|
|
36
|
+
upsert,
|
|
37
|
+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
|
|
38
|
+
const provider = await this.ensureConnected();
|
|
39
|
+
const result = await provider.updateMany(database, collection, filter, update, {
|
|
40
|
+
upsert,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let message = "";
|
|
44
|
+
if (result.matchedCount === 0) {
|
|
45
|
+
message = `No documents matched the filter.`;
|
|
46
|
+
} else {
|
|
47
|
+
message = `Matched ${result.matchedCount} document(s).`;
|
|
48
|
+
if (result.modifiedCount > 0) {
|
|
49
|
+
message += ` Modified ${result.modifiedCount} document(s).`;
|
|
50
|
+
}
|
|
51
|
+
if (result.upsertedCount > 0) {
|
|
52
|
+
message += ` Upserted ${result.upsertedCount} document(s) (with id: ${result.upsertedId?.toString()}).`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
text: message,
|
|
60
|
+
type: "text",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|