[Server] Enforce x-mcp-header where a request uses it (SEP-2243) - #453
Merged
chr-hertel merged 2 commits intoAug 18, 2026
Merged
Conversation
chr-hertel
requested review from
CodeWithKyrian,
Nyholm and
soyuka
as code owners
August 18, 2026 19:10
Only top-level properties were inspected, so an annotation on a nested property was silently unenforced - the header could say anything. The walk now follows a chain of properties keys to any depth and reads the argument at that exact path. Integer values compare numerically, so a client writing 42.0 for a body value of 42 is no longer rejected.
chr-hertel
force-pushed
the
pr/2026-header-mirroring
branch
from
August 18, 2026 22:24
381ae15 to
ec1b4fb
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the server-side StandardHeaderValidator to correctly enforce x-mcp-header annotations beyond top-level tool input properties, ensuring nested Mcp-Param-* headers are validated against the exact body argument path. It also relaxes numeric comparison so integer header/body values can agree even when serialized differently (e.g., 42 vs 42.0).
Changes:
- Recursively discover
x-mcp-headerannotations through nestedpropertieschains and validate headers against the corresponding nested argument path. - Add numeric-aware comparison for mirrored params to avoid rejecting equivalent integer values with different decimal renderings.
- Extend unit tests to cover nested mirroring discovery, unreachable annotations, nested argument validation, and numeric comparison behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/Server/Stateless/StandardHeaderValidator.php |
Recursively extracts mirrored properties and validates Mcp-Param-* values against nested arguments; adds numeric comparison behavior. |
tests/Unit/Server/Stateless/StandardHeaderValidatorTest.php |
Adds coverage for nested x-mcp-header discovery and nested/header numeric validation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
is_numeric() and loose == both treat a string like "042" as equal to "42", so a string argument could pass header validation against a header that doesn't match it exactly. Gate the numeric comparison on the argument's actual type and a decimal-only header instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #452. The request-side half of
x-mcp-header; the definition-side half merged as #441.StandardHeaderValidatorinspected only top-level properties, so an annotation on a nested property was silently unenforced — theMcp-Param-*header could say anything. The walk now follows a chain ofpropertieskeys to any depth and reads the argument at that exact path.Integer values compare numerically, so a client writing
42.0for a body value of42is no longer rejected.Part of SEP-2243.
Cross-fork PRs can only target
main, so this diff also carries its ancestors until they merge. Only the last commit(s) belong to this PR — GitHub's "Commits" tab separates them.