8000 feat: add data access tools (#14) · mongodb-js/mongodb-mcp-server@a2237d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit a2237d2

Browse files
authored
feat: add data access tools (#14)
1 parent 9edf3ca commit a2237d2

31 files changed

+5072
-708
lines changed

package-lock.json

Lines changed: 4120 additions & 679 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"publishConfig": {
1515
"access": "public"
1616
},
17+
"type": "module",
1718
"scripts": {
1819
"prepare": "npm run build",
1920
"build:clean": "rm -rf dist",
@@ -40,11 +41,16 @@
4041
"globals": "^16.0.0",
4142
"prettier": "^3.5.3",
4243
"typescript": "^5.8.2",
43-
"typescript-eslint": "^8.29.1",
44-
"zod": "^3.24.2"
44+
"typescript-eslint": "^8.29.1"
4545
},
4646
"dependencies": {
47-
"@types/express": "^5.0.1"
47+
"@mongodb-js/devtools-connect": "^3.7.2",
48+
"@mongosh/service-provider-node-driver": "^3.6.0",
49+
"@types/express": "^5.0.1",
50+
"bson": "^6.10.3",
51+
"mongodb": "^6.15.0",
52+
"mongodb-schema": "^12.6.2",
53+
"zod": "^3.24.2"
4854
},
4955
"engines": {
5056
"node": ">=23.0.0"

src/config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import path from "path";
22
import fs from "fs";
3+
import { fileURLToPath } from "url";
34

4-
const packageMetadata = fs.readFileSync(path.resolve("./package.json"), "utf8");
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = path.dirname(__filename);
7+
8+
const packageMetadata = fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8");
59
const packageJson = JSON.parse(packageMetadata);
610

711
export const config = {

src/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum ErrorCodes {
2+
NotConnectedToMongoDB = 1_000_000,
3+
InvalidParams = 1_000_001,
4+
}

src/tools/atlas/atlasTool.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { ZodRawShape } from "zod";
21
import { ToolBase } from "../tool.js";
32
import { ApiClient } from "../../client.js";
43
import { State } from "../../state.js";
54

6-
export abstract class AtlasToolBase<Args extends ZodRawShape = ZodRawShape> extends ToolBase<Args> {
5+
export abstract class AtlasToolBase extends ToolBase {
76
constructor(
87
state: State,
98
protected apiClient: ApiClient

src/tools/atlas/listClusters.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import { ensureAuthenticated } from "./auth.js";
55
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
66
import { AtlasToolBase } from "./atlasTool.js";
77
import { State } from "../../state.js";
8+
import { ToolArgs } from "../tool.js";
89

9-
export class ListClustersTool extends AtlasToolBase<{
10-
projectId: ZodString | ZodOptional<ZodString>;
11-
}> {
10+
export class ListClustersTool extends AtlasToolBase {
1211
protected name = "listClusters";
1312
protected description = "List MongoDB Atlas clusters";
1413
protected argsShape;
@@ -29,7 +28,7 @@ export class ListClustersTool extends AtlasToolBase<{
2928
};
3029
}
3130

32-
protected async execute({ projectId }: { projectId: string }): Promise<CallToolResult> {
31+
protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
3332
await ensureAuthenticated(this.state, this.apiClient);
3433

3534
let clusters: AtlasCluster[] | undefined = undefined;

src/tools/atlas/tools.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { ZodRawShape } from "zod";
21
import { ToolBase } from "../tool.js";
32
import { ApiClient } from "../../client.js";
43
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -8,7 +7,7 @@ import { ListClustersTool } from "./listClusters.js";
87
import { ListProjectsTool } from "./listProjects.js";
98

109
export function registerAtlasTools(server: McpServer, state: State, apiClient: ApiClient) {
11-
const tools: ToolBase<ZodRawShape>[] = [
10+
const tools: ToolBase[] = [
1211
new AuthTool(state, apiClient),
1312
new ListClustersTool(state, apiClient),
1413
new ListProjectsTool(state, apiClient),
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
3+
import { ToolArgs } 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: DbOperationType = "read";
10+
11+
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
12+
const provider = 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+
}

src/tools/mongodb/connect.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { z } from "zod";
2+
import { CallToolResult, McpError } from "@modelcontextprotocol/sdk/types.js";
3+
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
4+
import { DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
5+
import { ToolArgs } from "../tool";
6+
import { ErrorCodes } from "../../errors.js";
7+
8+
export class ConnectTool extends MongoDBToolBase {
9+
protected name = "connect";
10+
protected description = "Connect to a MongoDB instance";
11+
protected argsShape = {
12+
connectionStringOrClusterName: z
13+
.string()
14+
.optional()
15+
.describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format) or cluster name"),
16+
};
17+
18+
protected operationType: DbOperationType = "metadata";
19+
20+
protected async execute({
21+
connectionStringOrClusterName,
22+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
23+
if (!connectionStringOrClusterName) {
24+
// TODO: try reconnecting to the default connection
25+
return {
26+
content: [
27+
{ type: "text", text: "No connection details provided." },
28+
{ type: "text", text: "Please provide either a connection string or a cluster name" },
29+
{
30+
type: "text",
31+
text: "Alternatively, you can use the default deployment at mongodb://localhost:27017",
32+
},
33+
],
34+
};
35+
}
36+
37+
let connectionString: string;
38+
39+
if (typeof connectionStringOrClusterName === "string") {
40+
if (
41+
connectionStringOrClusterName.startsWith("mongodb://") ||
42+
connectionStringOrClusterName.startsWith("mongodb+srv://")
43+
) {
44+
connectionString = connectionStringOrClusterName;
45+
} else {
46+
// TODO:
47+
return {
48+
content: [
49+
{
50+
type: "text",
51+
text: `Connecting via cluster name not supported yet. Please provide a connection string.`,
52+
},
53+
],
54+
};
55+
}
56+
} else {
57+
throw new McpError(ErrorCodes.InvalidParams, "Invalid connection options");
58+
}
59+
60+
await this.connect(connectionString);
61+
62+
return {
63+
content: [{ type: "text", text: `Successfully connected to ${connectionString}.` }],
64+
};
65+
}
66+
67+
private async connect(connectionString: string): Promise<void> {
68+
const provider = await NodeDriverServiceProvider.connect(connectionString, {
69+
productDocsLink: "https://docs.mongodb.com/todo-mcp",
70+
productName: "MongoDB MCP",
71+
});
72+
73+
this.mongodbState.serviceProvider = provider;
74+
}
75+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
5+
6+
export class InsertManyTool extends MongoDBToolBase {
7+
protected name = "insert-many";
8+
protected description = "Insert an array of documents into a MongoDB collection";
9+
protected argsShape = {
10+
...DbOperationArgs,
11+
documents: z
12+
.array(z.object({}).passthrough().describe("An individual MongoDB document"))
13+
.describe(
14+
"The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()"
15+
),
16+
};
17+
protected operationType: DbOperationType = "create";
18+
19+
protected async execute({
20+
database,
21+
collection,
22+
documents,
23+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
24+
const provider = this.ensureConnected();
25+
const result = await provider.insertMany(database, collection, documents);
26+
27+
return {
28+
content: [
29+
{
30+
text: `Inserted \`${result.insertedCount}\` documents into collection \`${collection}\``,
31+
type: "text",
32+
},
33+
{
34+
text: `Inserted IDs: ${Object.values(result.insertedIds).join(", ")}`,
35+
type: "text",
36+
},
37+
],
38+
};
39+
}
40+
}

src/tools/mongodb/create/insertOne.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
5+
6+
export class InsertOneTool extends MongoDBToolBase {
7+
protected name = "insert-one";
8+
protected description = "Insert a document into a MongoDB collection";
9+
protected argsShape = {
10+
...DbOperationArgs,
11+
document: z
12+
.object({})
13+
.passthrough()
14+
.describe(
15+
"The document to insert, matching the syntax of the document argument of db.collection.insertOne()"
16+
),
17+
};
18+
19+
protected operationType: DbOperationType = "create";
20+
21+
protected async execute({
22+
database,
23+
collection,
24+
document,
25+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26+
const provider = this.ensureConnected();
27+
const result = await provider.insertOne(database, collection, document);
28+
29+
return {
30+
content: [
31+
{
32+
text: `Inserted document with ID \`${result.insertedId}\` into collection \`${collection}\``,
33+
type: "text",
34+
},
35+
],
36+
};
37+
}
38+
}

src/tools/mongodb/createIndex.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
4+
import { ToolArgs } from "../tool.js";
5+
import { IndexDirection } from "mongodb";
6+
7+
export class CreateIndexTool extends MongoDBToolBase {
8+
protected name = "create-index";
9+
protected description = "Create an index for a collection";
10+
protected argsShape = {
11+
...DbOperationArgs,
12+
keys: z.record(z.string(), z.custom<IndexDirection>()).describe("The index definition"),
13+
};
14+
15+
protected operationType: DbOperationType = "create";
16+
17+
protected async execute({ database, collection, keys }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
18+
const provider = this.ensureConnected();
19+
const indexes = await provider.createIndexes(database, collection, [
20+
{
21+
key: keys,
22+
},
23+
]);
24+
25+
return {
26+
content: [
27+
{
28+
text: `Created the index \`${indexes[0]}\``,
29+
type: "text",
30+
},
31+
],
32+
};
33+
}
34+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
5+
6+
export class DeleteManyTool extends MongoDBToolBase {
7+
protected name = "delete-many";
8+
protected description = "Removes all documents that match the filter from a MongoDB collection";
9+
protected argsShape = {
10+
...DbOperationArgs,
11+
filter: z
12+
.object({})
13+
.passthrough()
14+
.optional()
15+
.describe(
16+
"The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()"
17+
),
18+
};
19+
protected operationType: DbOperationType = "delete";
20+
21+
protected async execute({
22+
database,
23+
collection,
24+
filter,
25+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26+
const provider = this.ensureConnected();
27+
const result = await provider.deleteMany(database, collection, filter);
28+
29+
return {
30+
content: [
31+
{
32+
text: `Deleted \`${result.deletedCount}\` documents from collection \`${collection}\``,
33+
type: "text",
34+
},
35+
],
36+
};
37+
}
38+
}

src/tools/mongodb/delete/deleteOne.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
5+
6+
export class DeleteOneTool extends MongoDBToolBase {
7+
protected name = "delete-one";
8+
protected description = "Removes a single document that match the filter from a MongoDB collection";
9+
protected argsShape = {
10+
...DbOperationArgs,
11+
filter: z
12+
.object({})
13+
.passthrough()
14+
.optional()
15+
.describe(
16+
"The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()"
17+
),
18+
};
19+
protected operationType: DbOperationType = "delete";
20+
21+
protected async execute({
22+
database,
23+
collection,
24+
filter,
25+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26+
const provider = this.ensureConnected();
27+
const result = await provider.deleteOne(database, collection, filter);
28+
29+
return {
30+
content: [
31+
{
32+
text: `Deleted \`${result.deletedCount}\` documents from collection \`${collection}\``,
33+
type: "text",
34+
},
35+
],
36+
};
37+
}
38+
}

0 commit comments

Comments
 (0)
0