Skip to content

Commit ce02189

Browse files
authored
fix: prevent $out and $merge stages when create/update/delete operations are disabled MCP-196 (#545)
1 parent d022c5b commit ce02189

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

src/tools/mongodb/read/aggregate.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,23 @@ export class AggregateTool extends MongoDBToolBase {
4949
}
5050

5151
private assertOnlyUsesPermittedStages(pipeline: Record<string, unknown>[]): void {
52-
if (!this.config.readOnly) {
52+
const writeOperations: OperationType[] = ["update", "create", "delete"];
53+
let writeStageForbiddenError = "";
54+
55+
if (this.config.readOnly) {
56+
writeStageForbiddenError = "In readOnly mode you can not run pipelines with $out or $merge stages.";
57+
} else if (this.config.disabledTools.some((t) => writeOperations.includes(t as OperationType))) {
58+
writeStageForbiddenError =
59+
"When 'create', 'update', or 'delete' operations are disabled, you can not run pipelines with $out or $merge stages.";
60+
}
61+
62+
if (!writeStageForbiddenError) {
5363
return;
5464
}
5565

5666
for (const stage of pipeline) {
5767
if (stage.$out || stage.$merge) {
58-
throw new MongoDBError(
59-
ErrorCodes.ForbiddenWriteOperation,
60-
"In readOnly mode you can not run pipelines with $out or $merge stages."
61-
);
68+
throw new MongoDBError(ErrorCodes.ForbiddenWriteOperation, writeStageForbiddenError);
6269
}
6370
}
6471
}

tests/integration/tools/mongodb/read/aggregate.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import {
44
validateThrowsForInvalidArguments,
55
getResponseContent,
66
} from "../../../helpers.js";
7-
import { expect, it } from "vitest";
7+
import { expect, it, afterEach } from "vitest";
88
import { describeWithMongoDB, getDocsFromUntrustedContent, validateAutoConnectBehavior } from "../mongodbHelpers.js";
99

1010
describeWithMongoDB("aggregate tool", (integration) => {
11+
afterEach(() => {
12+
integration.mcpServer().userConfig.readOnly = false;
13+
integration.mcpServer().userConfig.disabledTools = [];
14+
});
15+
1116
validateToolMetadata(integration, "aggregate", "Run an aggregation against a MongoDB collection", [
1217
...databaseCollectionParameters,
1318
{
@@ -129,6 +134,42 @@ describeWithMongoDB("aggregate tool", (integration) => {
129134
);
130135
});
131136

137+
for (const disabledOpType of ["create", "update", "delete"] as const) {
138+
it(`can not run $out stages when ${disabledOpType} operation is disabled`, async () => {
139+
await integration.connectMcpClient();
140+
integration.mcpServer().userConfig.disabledTools = [disabledOpType];
141+
const response = await integration.mcpClient().callTool({
142+
name: "aggregate",
143+
arguments: {
144+
database: integration.randomDbName(),
145+
collection: "people",
146+
pipeline: [{ $out: "outpeople" }],
147+
},
148+
});
149+
const content = getResponseContent(response);
150+
expect(content).toEqual(
151+
"Error running aggregate: When 'create', 'update', or 'delete' operations are disabled, you can not run pipelines with $out or $merge stages."
152+
);
153+
});
154+
155+
it(`can not run $merge stages when ${disabledOpType} operation is disabled`, async () => {
156+
await integration.connectMcpClient();
157+
integration.mcpServer().userConfig.disabledTools = [disabledOpType];
158+
const response = await integration.mcpClient().callTool({
159+
name: "aggregate",
160+
arguments: {
161+
database: integration.randomDbName(),
162+
collection: "people",
163+
pipeline: [{ $merge: "outpeople" }],
164+
},
165+
});
166+
const content = getResponseContent(response);
167+
expect(content).toEqual(
168+
"Error running aggregate: When 'create', 'update', or 'delete' operations are disabled, you can not run pipelines with $out or $merge stages."
169+
);
170+
});
171+
}
172+
132173
validateAutoConnectBehavior(integration, "aggregate", () => {
133174
return {
134175
args: {

0 commit comments

Comments
 (0)