-
Notifications
You must be signed in to change notification settings - Fork 166
feat: add ability to override parameters using HTTP headers MCP-293 #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
src/transports/base.ts
Outdated
| protected async setupServer(): Promise<Server> { | ||
| protected async setupServer(request?: RequestContext): Promise<Server> { | ||
| // Apply config overrides from request context (headers and query parameters) | ||
| let userConfig = applyConfigOverrides({ baseConfig: this.userConfig, request }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we make this config override opt-in?
Can it be problematic that people who update the MCP server don't release that others can override some other settings now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the override should be opt-in using the --allowRequestOverrides flag. Just realised that I did not write this info in the ticket but its further explained in Goal#3 in the scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds the ability to override configuration parameters via HTTP headers and query parameters when using the HTTP transport. Overrides are applied automatically from request context before the createSessionConfig hook runs, with query parameters taking precedence over headers. Each config field defines its override behavior (not-allowed, override, merge, or custom logic).
Key changes:
- New config override system with three behaviors: "not-allowed" (security-sensitive fields), "override" (simple replacement), and "merge" (arrays)
- Conditional overrides for security flags (readOnly, indexCheck, disableEmbeddingsValidation) using custom logic
- Request context (headers/query) passed through transport layer to config application
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/common/config/configOverrides.ts | New module implementing config override extraction and application logic |
| src/common/config/configUtils.ts | Added override behavior types, parseBoolean preprocessor, and oneWayOverride helper |
| src/common/config/userConfig.ts | Registered override behaviors for all config fields and added boolean preprocessors |
| src/transports/base.ts | Modified setupServer to accept RequestContext and apply overrides before createSessionConfig |
| src/transports/streamableHttp.ts | Extracts headers/query into RequestContext and passes to setupServer |
| tests/unit/common/config/configOverrides.test.ts | Comprehensive unit tests for override behaviors and edge cases |
| tests/integration/transports/configOverrides.test.ts | Integration tests verifying overrides work end-to-end via HTTP |
| tests/integration/transports/createSessionConfig.test.ts | Refactored to use helpers and verify createSessionConfig receives request context |
| tests/integration/server.test.ts | Updated test to check for insert-many instead of insert-one |
| package.json | Added --run flag to test script |
Pull Request Test Coverage Report for Build 19679690652Details
💛 - Coveralls |
src/transports/base.ts
Outdated
| protected async setupServer(): Promise<Server> { | ||
| protected async setupServer(request?: RequestContext): Promise<Server> { | ||
| // Apply config overrides from request context (headers and query parameters) | ||
| let userConfig = applyConfigOverrides({ baseConfig: this.userConfig, request }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the override should be opt-in using the --allowRequestOverrides flag. Just realised that I did not write this info in the ticket but its further explained in Goal#3 in the scope.
nirinchev
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked at the tests yet, but I figured it's better to leave the comments I have so far so you don't wait for the full review.
src/common/config/configOverrides.ts
Outdated
| return behavior(baseValue, overrideValue); | ||
| } catch (error) { | ||
| throw new Error( | ||
| `Cannot apply override for ${key} from ${JSON.stringify(baseValue)} to ${JSON.stringify(overrideValue)}: ${error instanceof Error ? error.message : String(error)}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has the potential to leak the base value to the client - do we want that?
src/common/config/configOverrides.ts
Outdated
| if (Array.isArray(baseValue) && Array.isArray(overrideValue)) { | ||
| return [...(baseValue as unknown[]), ...(overrideValue as unknown[])]; | ||
| } | ||
| throw new Error("Cannot merge non-array values, did you mean to use the 'override' behavior?"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include the key here? Otherwise it's kind of hard to know what you did wrong.
| * Preprocessor for boolean values that handles string "true"/"false" correctly. | ||
| * Zod's coerce.boolean() treats any non-empty string as true, which is not what we want. | ||
| */ | ||
| export function parseBoolean(val: unknown): unknown { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function appears to treat any non-empty string as true too (except false and 0) - is this intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, this was a good balance between making sure old behaviors will work similar way (so if someone has --readonly=yes before, it'd still be true and not suddenly become false. while also making sure intentional setting of the value works too.
This helps us have the same logic for CLI and request parsing/validation. Though open to adjusting if there's concerns here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should just error out if the string value is different from true/false/1/0? Because based on the same logic, if we have --readonly=no, that would now make the server readonly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to this but it does mean this is a breaking change.
in latest v1.2.0 --readOnly no would make the server readOnly
--readOnly false would make the server write-able
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the question is if this is intentional - I would say this is more of a bugfix than a breaking change. Additionally, if we throw, it won't be a silent breaking change - we'll give users feedback about what to fix, which I think is a reasonable compromise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay apparently this preprocessing only really applies to these headers.
everything else is configured by yargs-parser... so it's about changing settings there
src/common/config/userConfig.ts
Outdated
| .default(300_000) | ||
| .describe("Time in milliseconds after which an export is considered expired and eligible for cleanup."), | ||
| .describe("Time in milliseconds after which an export is considered expired and eligible for cleanup.") | ||
| .register(configRegistry, { overrideBehavior: "override" }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds sketchy - can I setup my exports to timeout after a year, then flood the server storage with exports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. But what if the client wanted a smaller timeout of their export? We can cap the overridable max timeout to the server configured timeout? But yea if it becomes too complex, I am fine leaving it as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting this to onlyLowerThanBaseValueOverride
src/common/config/userConfig.ts
Outdated
| .describe("When set to true, disables validation of embeddings dimensions."), | ||
| .describe("When set to true, disables validation of embeddings dimensions.") | ||
| .register(configRegistry, { | ||
| overrideBehavior: oneWayOverride(false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder why this is oneWayOverride?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if validation is enabled (i.e. disableValidation is false) then we would not want a client to disable validation and let the MCP server insert arbitrary data in a vector search field (which apparently breaks vector search)
By the way, I find the double negative to be confusing so it'd be great to rename this in general
src/common/config/userConfig.ts
Outdated
| .default([]) | ||
| .describe("An array of preview features that are enabled."), | ||
| .describe("An array of preview features that are enabled.") | ||
| .register(configRegistry, { overrideBehavior: "merge" }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we merging these arrays?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just following the scope document. Agree some can be adjusted cc: @himanshusinghs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is based on understanding that individual clients might want to enable specific preview features for their session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should either be override (so you can remove features if needed) or not-allowed (so no experimental features can be enabled)
…gagik/query-header-override
baf9bbe to
d2731d8
Compare
This adds header and query param-based overrides.