Skip to content

Commit 82f38d0

Browse files
committed
chore: add allowRequestOverrides
1 parent c21a707 commit 82f38d0

File tree

5 files changed

+92
-0
lines changed

5 files changed

+92
-0
lines changed

src/common/config/configOverrides.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export function applyConfigOverrides({
2525
return baseConfig;
2626
}
2727

28+
// Only apply overrides if allowRequestOverrides is enabled
29+
if (!baseConfig.allowRequestOverrides) {
30+
return baseConfig;
31+
}
32+
2833
const result: UserConfig = { ...baseConfig };
2934
const overridesFromHeaders = extractConfigOverrides("header", request.headers);
3035
const overridesFromQuery = extractConfigOverrides("query", request.query);

src/common/config/userConfig.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,11 @@ export const UserConfigSchema = z4.object({
207207
.default([])
208208
.describe("An array of preview features that are enabled.")
209209
.register(configRegistry, { overrideBehavior: "merge" }),
210+
allowRequestOverrides: z4
211+
.preprocess(parseBoolean, z4.boolean())
212+
.default(false)
213+
.describe(
214+
"When set to true, allows configuration values to be overridden via request headers and query parameters."
215+
)
216+
.register(configRegistry, { overrideBehavior: "not-allowed" }),
210217
});

tests/integration/transports/configOverrides.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,34 @@ describe("Config Overrides via HTTP", () => {
4848
});
4949

5050
describe("override behavior", () => {
51+
it("should not apply overrides when allowRequestOverrides is false", async () => {
52+
await startRunner({
53+
...defaultTestConfig,
54+
httpPort: 0,
55+
readOnly: false,
56+
allowRequestOverrides: false,
57+
});
58+
59+
await connectClient({
60+
["x-mongodb-mcp-read-only"]: "true",
61+
});
62+
63+
const response = await client.listTools();
64+
65+
expect(response).toBeDefined();
66+
expect(response.tools).toBeDefined();
67+
68+
// Verify read-only mode is NOT applied - insert-many should still be available
69+
const writeTools = response.tools.filter((tool) => tool.name === "insert-many");
70+
expect(writeTools.length).toBe(1);
71+
});
72+
5173
it("should override readOnly config via header (false to true)", async () => {
5274
await startRunner({
5375
...defaultTestConfig,
5476
httpPort: 0,
5577
readOnly: false,
78+
allowRequestOverrides: true,
5679
});
5780

5881
await connectClient({
@@ -78,6 +101,7 @@ describe("Config Overrides via HTTP", () => {
78101
...defaultTestConfig,
79102
httpPort: 0,
80103
connectionString: undefined,
104+
allowRequestOverrides: true,
81105
});
82106

83107
await connectClient({
@@ -96,6 +120,7 @@ describe("Config Overrides via HTTP", () => {
96120
...defaultTestConfig,
97121
httpPort: 0,
98122
disabledTools: ["insert-many"],
123+
allowRequestOverrides: true,
99124
});
100125

101126
await connectClient({
@@ -158,6 +183,7 @@ describe("Config Overrides via HTTP", () => {
158183
await startRunner({
159184
...defaultTestConfig,
160185
httpPort: 0,
186+
allowRequestOverrides: true,
161187
});
162188

163189
try {
@@ -178,6 +204,7 @@ describe("Config Overrides via HTTP", () => {
178204
await startRunner({
179205
...defaultTestConfig,
180206
httpPort: 0,
207+
allowRequestOverrides: true,
181208
});
182209

183210
try {
@@ -208,6 +235,7 @@ describe("Config Overrides via HTTP", () => {
208235
...defaultTestConfig,
209236
httpPort: 0,
210237
readOnly: false,
238+
allowRequestOverrides: true,
211239
});
212240

213241
// Note: SDK doesn't support query params directly, so this test verifies the mechanism exists
@@ -230,6 +258,7 @@ describe("Config Overrides via HTTP", () => {
230258
...defaultTestConfig,
231259
httpPort: 0,
232260
readOnly: false,
261+
allowRequestOverrides: true,
233262
};
234263

235264
// createSessionConfig receives the config after header overrides are applied
@@ -274,6 +303,7 @@ describe("Config Overrides via HTTP", () => {
274303
const userConfig = {
275304
...defaultTestConfig,
276305
httpPort: 0,
306+
allowRequestOverrides: true,
277307
};
278308

279309
let capturedRequest: RequestContext | undefined;
@@ -308,6 +338,7 @@ describe("Config Overrides via HTTP", () => {
308338
...defaultTestConfig,
309339
httpPort: 0,
310340
readOnly: false,
341+
allowRequestOverrides: true,
311342
});
312343

313344
await connectClient({
@@ -332,6 +363,7 @@ describe("Config Overrides via HTTP", () => {
332363
...defaultTestConfig,
333364
httpPort: 0,
334365
readOnly: true,
366+
allowRequestOverrides: true,
335367
});
336368

337369
try {
@@ -360,6 +392,7 @@ describe("Config Overrides via HTTP", () => {
360392
indexCheck: false,
361393
idleTimeoutMs: 600_000,
362394
disabledTools: ["tool1"],
395+
allowRequestOverrides: true,
363396
});
364397

365398
await connectClient({

tests/tsconfig.json

Whitespace-only changes.

tests/unit/common/config/configOverrides.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("configOverrides", () => {
2020
exportTimeoutMs: 300_000,
2121
exportCleanupIntervalMs: 120_000,
2222
atlasTemporaryDatabaseUserLifetimeMs: 14_400_000,
23+
allowRequestOverrides: true,
2324
};
2425

2526
describe("helper functions", () => {
@@ -67,6 +68,51 @@ describe("configOverrides", () => {
6768
expect(result).toEqual(baseConfig);
6869
});
6970

71+
describe("allowRequestOverrides", () => {
72+
it("should not apply overrides when allowRequestOverrides is false", () => {
73+
const request: RequestContext = {
74+
headers: {
75+
"x-mongodb-mcp-read-only": "true",
76+
"x-mongodb-mcp-idle-timeout-ms": "300000",
77+
},
78+
};
79+
const configWithOverridesDisabled = {
80+
...baseConfig,
81+
allowRequestOverrides: false,
82+
} as UserConfig;
83+
const result = applyConfigOverrides({ baseConfig: configWithOverridesDisabled, request });
84+
// Config should remain unchanged
85+
expect(result.readOnly).toBe(false);
86+
expect(result.idleTimeoutMs).toBe(600_000);
87+
});
88+
89+
it("should apply overrides when allowRequestOverrides is true", () => {
90+
const request: RequestContext = {
91+
headers: {
92+
"x-mongodb-mcp-read-only": "true",
93+
"x-mongodb-mcp-idle-timeout-ms": "300000",
94+
},
95+
};
96+
const result = applyConfigOverrides({ baseConfig: baseConfig as UserConfig, request });
97+
// Config should be overridden
98+
expect(result.readOnly).toBe(true);
99+
expect(result.idleTimeoutMs).toBe(300000);
100+
});
101+
102+
it("should not apply overrides by default when allowRequestOverrides is not set", () => {
103+
const request: RequestContext = {
104+
headers: {
105+
"x-mongodb-mcp-read-only": "true",
106+
},
107+
};
108+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
109+
const { allowRequestOverrides, ...configWithoutOverridesFlag } = baseConfig;
110+
const result = applyConfigOverrides({ baseConfig: configWithoutOverridesFlag as UserConfig, request });
111+
// Should not apply overrides since the default is false
112+
expect(result.readOnly).toBe(false);
113+
});
114+
});
115+
70116
describe("override behavior", () => {
71117
it("should override boolean values with override behavior", () => {
72118
const request: RequestContext = {
@@ -162,6 +208,7 @@ describe("configOverrides", () => {
162208
"maxDocumentsPerQuery",
163209
"exportsPath",
164210
"voyageApiKey",
211+
"allowRequestOverrides",
165212
]);
166213
});
167214

0 commit comments

Comments
 (0)