Skip to content

Commit 5c53842

Browse files
authored
Merge branch 'main' into telemetry-data
2 parents 1bb08ea + 0584f38 commit 5c53842

33 files changed

+1321
-204
lines changed

src/tools/mongodb/metadata/dbStats.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
22
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
33
import { ToolArgs, OperationType } from "../../tool.js";
4+
import { EJSON } from "bson";
45

56
export class DbStatsTool extends MongoDBToolBase {
67
protected name = "db-stats";
@@ -21,7 +22,11 @@ export class DbStatsTool extends MongoDBToolBase {
2122
return {
2223
content: [
2324
{
24-
text: `Statistics for database ${database}: ${JSON.stringify(result)}`,
25+
text: `Statistics for database ${database}`,
26+
type: "text",
27+
},
28+
{
29+
text: EJSON.stringify(result),
2530
type: "text",
2631
},
2732
],

src/tools/mongodb/metadata/explain.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,24 @@ export class ExplainTool extends MongoDBToolBase {
4747
const method = methods[0];
4848

4949
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`");
5151
}
5252

5353
let result: Document;
5454
switch (method.name) {
5555
case "aggregate": {
5656
const { pipeline } = method.arguments;
57-
result = await provider.aggregate(database, collection, pipeline).explain(ExplainTool.defaultVerbosity);
57+
result = await provider
58+
.aggregate(
59+
database,
60+
collection,
61+
pipeline,
62+
{},
63+
{
64+
writeConcern: undefined,
65+
}
66+
)
67+
.explain(ExplainTool.defaultVerbosity);
5868
break;
5969
}
6070
case "find": {
@@ -66,18 +76,21 @@ export class ExplainTool extends MongoDBToolBase {
6676
}
6777
case "count": {
6878
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;
79+
result = await provider.mongoClient.db(database).command({
80+
explain: {
81+
count: collection,
82+
query,
83+
},
84+
verbosity: ExplainTool.defaultVerbosity,
85+
});
7386
break;
7487
}
7588
}
7689

7790
return {
7891
content: [
7992
{
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.`,
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.`,
8194
type: "text",
8295
},
8396
{

src/tools/mongodb/read/aggregate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
5+
import { EJSON } from "bson";
56

67
export const AggregateArgs = {
78
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"),
99
};
1010

1111
export class AggregateTool extends MongoDBToolBase {
@@ -27,12 +27,12 @@ export class AggregateTool extends MongoDBToolBase {
2727

2828
const content: Array<{ text: string; type: "text" }> = [
2929
{
30-
text: `Found ${documents.length} documents in the collection \`${collection}\`:`,
30+
text: `Found ${documents.length} documents in the collection "${collection}":`,
3131
type: "text",
3232
},
3333
...documents.map((doc) => {
3434
return {
35-
text: JSON.stringify(doc),
35+
text: EJSON.stringify(doc),
3636
type: "text",
3737
} as { text: string; type: "text" };
3838
}),

src/tools/mongodb/read/collectionIndexes.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,36 @@ export class CollectionIndexesTool extends MongoDBToolBase {
1313
const indexes = await provider.getIndexes(database, collection);
1414

1515
return {
16-
content: indexes.map((indexDefinition) => {
17-
return {
18-
text: `Field: ${indexDefinition.name}: ${JSON.stringify(indexDefinition.key)}`,
16+
content: [
17+
{
18+
text: `Found ${indexes.length} indexes in the collection "${collection}":`,
1919
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+
],
2228
};
2329
}
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+
}
2448
}

src/tools/mongodb/read/find.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
55
import { SortDirection } from "mongodb";
6+
import { EJSON } from "bson";
67

78
export const FindArgs = {
89
filter: z
@@ -44,12 +45,12 @@ export class FindTool extends MongoDBToolBase {
4445

4546
const content: Array<{ text: string; type: "text" }> = [
4647
{
47-
text: `Found ${documents.length} documents in the collection \`${collection}\`:`,
48+
text: `Found ${documents.length} documents in the collection "${collection}":`,
4849
type: "text",
4950
},
5051
...documents.map((doc) => {
5152
return {
52-
text: JSON.stringify(doc),
53+
text: EJSON.stringify(doc),
5354
type: "text",
5455
} as { text: string; type: "text" };
5556
}),

src/tools/mongodb/update/renameCollection.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { MongoDBToolBase } from "../mongodbTool.js";
3+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
55

66
export class RenameCollectionTool extends MongoDBToolBase {
77
protected name = "rename-collection";
88
protected description = "Renames a collection in a MongoDB database";
99
protected argsShape = {
10-
collection: z.string().describe("Collection name"),
11-
database: z.string().describe("Database name"),
10+
...DbOperationArgs,
1211
newName: z.string().describe("The new name for the collection"),
1312
dropTarget: z.boolean().optional().default(false).describe("If true, drops the target collection if it exists"),
1413
};
@@ -28,10 +27,40 @@ export class RenameCollectionTool extends MongoDBToolBase {
2827
return {
2928
content: [
3029
{
31-
text: `Collection \`${collection}\` renamed to \`${result.collectionName}\` in database \`${database}\`.`,
30+
text: `Collection "${collection}" renamed to "${result.collectionName}" in database "${database}".`,
3231
type: "text",
3332
},
3433
],
3534
};
3635
}
36+
37+
protected handleError(
38+
error: unknown,
39+
args: ToolArgs<typeof this.argsShape>
40+
): Promise<CallToolResult> | CallToolResult {
41+
if (error instanceof Error && "codeName" in error) {
42+
switch (error.codeName) {
43+
case "NamespaceNotFound":
44+
return {
45+
content: [
46+
{
47+
text: `Cannot rename "${args.database}.${args.collection}" because it doesn't exist.`,
48+
type: "text",
49+
},
50+
],
51+
};
52+
case "NamespaceExists":
53+
return {
54+
content: [
55+
{
56+
text: `Cannot rename "${args.database}.${args.collection}" to "${args.newName}" because the target collection already exists. If you want to overwrite it, set the "dropTarget" argument to true.`,
57+
type: "text",
58+
},
59+
],
60+
};
61+
}
62+
}
63+
64+
return super.handleError(error, args);
65+
}
3766
}

src/tools/mongodb/update/updateMany.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { MongoDBToolBase } from "../mongodbTool.js";
3+
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
55

66
export class UpdateManyTool extends MongoDBToolBase {
77
protected name = "update-many";
88
protected description = "Updates all documents that match the specified filter for a collection";
99
protected argsShape = {
10-
collection: z.string().describe("Collection name"),
11-
database: z.string().describe("Database name"),
10+
...DbOperationArgs,
1211
filter: z
1312
.object({})
1413
.passthrough()
@@ -19,7 +18,6 @@ export class UpdateManyTool extends MongoDBToolBase {
1918
update: z
2019
.object({})
2120
.passthrough()
22-
.optional()
2321
.describe("An update document describing the modifications to apply using update operator expressions"),
2422
upsert: z
2523
.boolean()
@@ -41,15 +39,15 @@ export class UpdateManyTool extends MongoDBToolBase {
4139
});
4240

4341
let message = "";
44-
if (result.matchedCount === 0) {
45-
message = `No documents matched the filter.`;
42+
if (result.matchedCount === 0 && result.modifiedCount === 0 && result.upsertedCount === 0) {
43+
message = "No documents matched the filter.";
4644
} else {
4745
message = `Matched ${result.matchedCount} document(s).`;
4846
if (result.modifiedCount > 0) {
4947
message += ` Modified ${result.modifiedCount} document(s).`;
5048
}
5149
if (result.upsertedCount > 0) {
52-
message += ` Upserted ${result.upsertedCount} document(s) (with id: ${result.upsertedId?.toString()}).`;
50+
message += ` Upserted ${result.upsertedCount} document with id: ${result.upsertedId?.toString()}.`;
5351
}
5452
}
5553

0 commit comments

Comments
 (0)