Repository navigation
Conversation
…aracters Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com>
🦋 Changeset detectedLatest commit: f7476ad 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: |
|
Verified this against base c05225a plus this branch. On main, decodeMcpParamValue('café') returns 'café' because the non-sentinel path at packages/core-internal/src/shared/mcpParamHeaders.ts:276 hands back any plain value unchecked; this branch gates it at :279. Running pnpm vitest run test/shared/mcpParamHeaders.test.ts test/shared/standardHeaderValidation.test.ts in packages/core-internal gives 92 passed (62 + 30), while the same PR tests against base fail exactly once with "expected 'café' to be undefined". packages/server test/server/mcpParamValidation.test.ts passes 9/9. The interior-tab case ('a\tb') still decodes, matching the 0x09 allowance in the spec. |
|
Read the diff (not run here). The shared Two checks I could not see in the tests: (1) |
…cted Both rejections read "invalid characters or an invalid Base64 sentinel value", so a client author could not tell a plain value that needed Base64 wrapping from a broken sentinel. The message now names the one that applies; the cell stays *-invalid-encoding for both. Also pin DEL (0x7F) and a C0 control (0x01) as rejected plain values, since the earlier tests only used a non-ASCII character. Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com>
chrikrah
left a comment
There was a problem hiding this comment.
@arhancanli the DEL/C0 tests and the message are in the new commit. I left out a real http.createServer test, because a Node request goes through toWebRequest and reaches the same decodeMcpParamValue check the café case pins. The message was not enough: it now says a plain value with a character outside 0x20-0x7E / 0x09, which has to be sent Base64-wrapped for your mojibake case and an invalid Base64 sentinel value for a broken sentinel. The cell stays *-invalid-encoding for both, since the spec gives them one row. The test now pins a\x7fb and a\x01b as rejected next to café.
$ pnpm exec vitest run test/shared/mcpParamHeaders.test.ts test/shared/standardHeaderValidation.test.ts # packages/core-internal, new commit on effb6d6, Node v22.23.3
Tests 92 passed (92)
# both src files from effb6d6, new assertions kept
Tests 2 failed | 90 passed (92) # the sentinel and the plain-value message checks
$ pnpm exec vitest run test/server/mcpParamValidation.test.ts # packages/server
Tests 9 passed (9)
$ pnpm run check # packages/core-internal: tsgo, eslint, prettier
All matched files use Prettier code style!
You raised the client-author view, so does the plain-value wording tell you what to fix?
|
Read One edge to check: a value that starts with |
A value that starts with =?base64? but lacks the ?= suffix is not a sentinel, so it decodes as a literal when its characters are plain and is rejected with the plain-value message when they are not. Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com>
chrikrah
left a comment
There was a problem hiding this comment.
@arhancanli It's the plain path on purpose: SEP-2243's edge-case table lists the missing-suffix value as a literal, so the plain-character check then applies as usual. It had no test, and the new commit pins it. One overlap stays as it was before this PR: the prefix followed by a single = reuses the prefix's closing question mark as its suffix, matches the spec's starts-with/ends-with pattern, and decodes to the empty string.
# packages/core-internal, Node v22.23.3, new test on top of abf71d5
$ pnpm exec vitest run test/shared/mcpParamHeaders.test.ts test/shared/standardHeaderValidation.test.ts
Tests 92 passed (92) # abf71d5
Tests 93 passed (93) # with the new test
# '=?base64?SGVsbG8=' -> decoded as the literal
# '=?base64?café' -> rejected, "has to be sent Base64-wrapped"
# with decode/describe keyed on the prefix alone, same tests:
Tests 1 failed | 92 passed (93)
$ pnpm run check
All matched files use Prettier code style!
$ node -e 'const v="=?base64?=";console.log(v.startsWith("=?base64?")&&v.endsWith("?="), JSON.stringify(v.slice(9,v.length-2)))'
true ""
That's the last open point from your read, so the branch stays as it is now until a maintainer reviews it.
The server accepts a plain
Mcp-Param-*orMcp-Namevalue that holds a character outside0x20–0x7Eand tab, becausedecodeMcpParamValue(packages/core-internal/src/shared/mcpParamHeaders.ts:274) returns any value without the Base64 sentinel as it is. The 2026-07-28 spec says servers MUST reject such anMcp-Param-*header. ForMcp-Name, Server Validation lists "a header value contains invalid characters" among the400/-32020failures. Its Value Encoding section says the client must Base64-wrap such a value.Node's HTTP parser passes a raw
0xE9byte through asé, so the value reachescreateMcpHandler. Before, at c05225a, throughhttp.createServerand a raw socket:After:
The new check uses the same character set as
needsBase64in the encoder. Both server checks get it:Mcp-Param-*andMcp-NameinvalidateStandardRequestHeaders. The cost: a client that sends raw Latin-1 in these headers now gets400/-32020, not200. The client in this SDK already wraps such values.v1.xhas no SEP-2243 code, so this is formainonly. A changeset is included. There is no issue for this.Verification
@felixweinberger, I kept the
*-invalid-encodingcells for this rejection because the wire answer is the same-32020. Do you want a separate*-invalid-characterscell for it?