fix(server): reject unsafe integers in x-mcp-header parameters when header is absent - #2772
Open
Yudis-bit wants to merge 1 commit into
Open
Conversation
…eader is absent Per the Streamable HTTP specification, integer values in `x-mcp-header` fields MUST be within the JavaScript safe-integer range (−2^53+1 to 2^53−1). Previously, `validateMcpParamHeaders` executed `continue` when `mcpParamPrimitiveToString(bodyRaw)` returned `undefined`, assuming that this only occurred when the body carried a non-primitive object/array belonging to schema validation. However, `mcpParamPrimitiveToString` also returns `undefined` for unsafe integers and non-finite numbers. This caused `tools/call` requests with annotated unsafe integer arguments (such as 9007199254740992) to skip parity validation completely when the client omitted the `Mcp-Param-*` header, invoking the tool handler instead of rejecting the non-conforming request. `validateMcpParamHeaders` now: 1. Only skips parity checks for non-primitive objects and functions. 2. Checks for absent headers (`headerValue === null`) across all primitive values and rejects with 400 / -32020 (`param-header-missing`). 3. Restricts numeric equality coercion (`numericComparable`) to safe integers so unsafe integers cannot be accepted via float precision loss. Fixes modelcontextprotocol#2689
🦋 Changeset detectedLatest commit: ce53d7d The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
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.
Fixes #2689.
What
Per the Streamable HTTP specification (Custom Headers from Tool Parameters):
And the server-side behavior table states:
Previously, in
validateMcpParamHeaders, the validation loop attempted to convertbodyRawviamcpParamPrimitiveToString(bodyRaw). When that returnedundefined, it executedcontinue, assuming thatbodyString === undefinedsolely indicated that the body carried a non-primitive object or array (which schema validation would catch at dispatch):However,
mcpParamPrimitiveToStringalso returnsundefinedfor numbers outside the safe-integer range (Number.isInteger(value) && !Number.isSafeInteger(value)) and non-finite numbers. This causedtools/callrequests carrying an annotated unsafe integer argument (e.g.9007199254740992) to skip parity validation entirely when the client omitted theMcp-Param-*header. Because JSON Schematype: "integer"accepts values outside the JavaScript safe integer range without custom range constraints, dispatch-time schema validation did not reject the call either, allowing the server to invoke the tool handler rather than rejecting the request.Furthermore, if a client provided the header for an unsafe integer,
numericComparablepreviously coerced it withNumber(decoded) === bodyRaw. AboveNumber.MAX_SAFE_INTEGER, IEEE 754 float precision loss means that different integers compare equal (e.g.Number('9007199254740993') === 9007199254740992evaluates totrue), which would incorrectly accept mismatched headers.Changes
packages/core-internal/src/shared/mcpParamHeaders.ts:bodyRawis truly a non-primitive (typeof bodyRaw === 'object' || typeof bodyRaw === 'function').headerValue === null) across all primitive values before string conversion, rejecting with400 Bad Request/-32020 HeaderMismatch(param-header-missing).numericComparableto safe numbers (isSafeNumeric:typeof bodyRaw === 'number' && Number.isFinite(bodyRaw) && (!Number.isInteger(bodyRaw) || Number.isSafeInteger(bodyRaw))). Unsafe integers and non-finite numbers are not numerically coerced, preventing false-equality matches due to floating-point precision loss.packages/core-internal/test/shared/mcpParamHeaders.test.ts:9_007_199_254_740_992) without a mirrored header rejects asparam-header-missing.param-header-mismatch.packages/server/test/server/mcpParamValidation.test.ts:tools/callwith an annotated integer parameter called with9_007_199_254_740_992and noMcp-Param-Countheader is rejected with400/-32020before the handler is invoked..changeset/reject-unsafe-integer-param-header.md:@modelcontextprotocol/core-internaland@modelcontextprotocol/server.Validation
pnpm run typecheck:all: 52 workspace projects pass with 0 errors.npx vitest run: all 69 test files (1,460 tests) pass in@modelcontextprotocol/core-internal.npx vitest run: all 42 test files (483 tests) pass in@modelcontextprotocol/server.npx prettier --checkclean across all modified files.Written with Antigravity agent against issue #2689 with minimal reproducible regression tests.