diff --git a/src/tools/mongodb/update/updateMany.ts b/src/tools/mongodb/update/updateMany.ts index 599115327..98a852911 100644 --- a/src/tools/mongodb/update/updateMany.ts +++ b/src/tools/mongodb/update/updateMany.ts @@ -47,6 +47,11 @@ export class UpdateManyTool extends MongoDBToolBase { const provider = await this.ensureConnected(); this.assertMqlIsAllowed(filter); + // The update document is also MQL and, in its aggregation-pipeline form, can carry + // server-side JS operators ($function/$where/$accumulator) and write stages ($out/$merge). + // It must go through the same guard as the filter so `disableServerSideJs` / readOnly / + // disabled-tools restrictions can't be bypassed via the update argument. + this.assertMqlIsAllowed(update); // Check if update operation uses an index if enabled if (this.config.indexCheck) { diff --git a/tests/integration/tools/mongodb/update/updateMany.test.ts b/tests/integration/tools/mongodb/update/updateMany.test.ts index 9a32f8878..3ccaef1f1 100644 --- a/tests/integration/tools/mongodb/update/updateMany.test.ts +++ b/tests/integration/tools/mongodb/update/updateMany.test.ts @@ -295,4 +295,33 @@ describeWithMongoDB("updateMany tool with server-side JavaScript operators", (in } }); } + + // The `update` document is MQL too, so it must go through the same guard as the `filter` + // (the regression this protects against). Unlike a filter, a classic update document does not + // execute the operator server-side, so the enabled case only asserts the guard lets it through. + for (const jsDisabled of [true, false]) { + it(`${jsDisabled ? "rejects" : "allows"} an update document carrying $function when disableServerSideJs is ${jsDisabled}`, async () => { + integration.mcpServer().userConfig.disableServerSideJs = jsDisabled; + await integration.connectMcpClient(); + const response = await integration.mcpClient().callTool({ + name: "update-many", + arguments: { + database: integration.randomDbName(), + collection: "people", + filter: {}, + update: { + $set: { + flagged: { $function: { body: "function () { return true; }", args: [], lang: "js" } }, + }, + }, + }, + }); + const content = getResponseContent(response.content); + if (jsDisabled) { + expect(content).toContain(`The "$function" operator is not allowed.`); + } else { + expect(content).not.toContain("server-side JavaScript operators"); + } + }); + } });