Skip to content

Commit a0bbbe9

Browse files
committed
feat: add explain tool
1 parent af90a87 commit a0bbbe9

File tree

3 files changed

+71
-67
lines changed

3 files changed

+71
-67
lines changed

src/tools/mongodb/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { UpdateManyTool } from "./update/updateMany.js";
2020
import { RenameCollectionTool } from "./update/renameCollection.js";
2121
import { DropDatabaseTool } from "./delete/dropDatabase.js";
2222
import { DropCollectionTool } from "./delete/dropCollection.js";
23+
import { ExplainTool } from "./metadata/explain.js";
2324

2425
export function registerMongoDBTools(server: McpServer, state: State) {
2526
const tools = [
@@ -43,6 +44,7 @@ export function registerMongoDBTools(server: McpServer, state: State) {
4344
RenameCollectionTool,
4445
DropDatabaseTool,
4546
DropCollectionTool,
47+
ExplainTool,
4648
];
4749

4850
for (const tool of tools) {

src/tools/mongodb/metadata/collectionExplain.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

src/tools/mongodb/metadata/explain.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
3+
import { ToolArgs } from "../../tool.js";
4+
import { z } from "zod";
5+
import { ExplainVerbosity, Document } from "mongodb";
6+
7+
export class ExplainTool extends MongoDBToolBase {
8+
protected name = "explain";
9+
protected description =
10+
"Returns statistics describing the execution of the winning plan chosen by the query optimizer for the evaluated method";
11+
12+
protected argsShape = {
13+
...DbOperationArgs,
14+
method: z.enum(["aggregate", "find"]).describe("The method to run"),
15+
methodArguments: z
16+
.object({
17+
aggregatePipeline: z
18+
.array(z.object({}).passthrough())
19+
.optional()
20+
.describe("aggregate - array of aggregation stages to execute"),
21+
22+
findQuery: z.object({}).passthrough().optional().describe("find - The query to run"),
23+
findProjection: z.object({}).passthrough().optional().describe("find - The projection to apply"),
24+
})
25+
.describe("The arguments for the method"),
26+
};
27+
28+
protected operationType: DbOperationType = "metadata";
29+
30+
protected async execute({
31+
database,
32+
collection,
33+
method,
34+
methodArguments,
35+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
36+
const provider = this.ensureConnected();
37+
38+
let result: Document;
39+
switch (method) {
40+
case "aggregate": {
41+
result = await provider.aggregate(database, collection).explain();
42+
break;
43+
}
44+
case "find": {
45+
const query = methodArguments.findQuery ?? {};
46+
const projection = methodArguments.findProjection ?? {};
47+
result = await provider
48+
.find(database, collection, query, { projection })
49+
.explain(ExplainVerbosity.queryPlanner);
50+
break;
51+
}
52+
default:
53+
throw new Error(`Unsupported method: ${method}`);
54+
}
55+
56+
return {
57+
content: [
58+
{
59+
text: `Here is some information about the winning plan chosen by the query optimizer for running the given \`${method}\` operation in \`${database}\`. This information can be used to understand how the query was executed and to optimize the query performance.`,
60+
type: "text",
61+
},
62+
{
63+
text: JSON.stringify(result),
64+
type: "text",
65+
},
66+
],
67+
};
68+
}
69+
}

0 commit comments

Comments
 (0)