') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(server): trim OWS from standard MCP headers by mattzcarey · Pull Request #2453 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

fix(server): trim OWS from standard MCP headers - #2453

Merged
mattzcarey merged 2 commits into
mainfrom
fix/trim-standard-header-ows
Jul 7, 2026
Merged

fix(server): trim OWS from standard MCP headers#2453
mattzcarey merged 2 commits into
mainfrom
fix/trim-standard-header-ows

Conversation

@mattzcarey

@mattzcareymattzcarey commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Fixes#2452.

What changed

  • strips RFC 9110 optional whitespace (SP / HTAB) around inbound MCP-Protocol-Version, Mcp-Method, and Mcp-Name values before modern request classification and validation;
  • uses a two-pointer character scan rather than a regular expression, keeping processing linear for untrusted header values;
  • keeps actual names containing surrounding whitespace distinct because those values use the existing Base64 sentinel encoding;
  • adds transport-neutral regressions for long OWS runs and non-OWS Unicode whitespace.

Why

@modelcontextprotocol/conformance@0.2.0-alpha.9's ServerAcceptsWhitespaceHeaderValue check fails against @modelcontextprotocol/server@2.0.0-beta.2 on workerd: the valid padded Mcp-Name is compared byte-for-byte with params.name and rejected as HeaderMismatch.

Verification

  • core-internal: 1,326 tests
  • pnpm check:all
  • pnpm build:all

@mattzcarey
mattzcarey requested a review from a team as a code ownerJuly 7, 2026 11:22
@changeset-bot

changeset-botBot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 12c5c3f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
NameType
@modelcontextprotocol/serverPatch
@modelcontextprotocol/core-internalPatch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2453

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2453

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2453

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2453

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2453

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2453

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2453

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2453

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2453

commit: 38dc1a4

Comment threadpackages/core-internal/src/shared/inboundClassification.ts Fixed
@mattzcarey
mattzcareyforce-pushed the fix/trim-standard-header-ows branch from e87170a to 12c5c3fCompareJuly 7, 2026 12:27
Comment on lines +841 to +851
// RFC 9110 §5.5: field parsing excludes optional whitespace around a
// field value. Fetch implementations normally perform this normalization,
// but transport-neutral callers and some runtimes can expose raw OWS.
request = {
...request,
...(request.protocolVersionHeader !== undefined && {
protocolVersionHeader: stripHttpOws(request.protocolVersionHeader)
}),
...(request.mcpMethodHeader !== undefined && { mcpMethodHeader: stripHttpOws(request.mcpMethodHeader) }),
...(request.mcpNameHeader !== undefined && { mcpNameHeader: stripHttpOws(request.mcpNameHeader) })
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: seems like something we might want to extract to a normalizeInboundHeaderFields function but seems fine rn

@felixweinbergerfelixweinberger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the CodeQL fix, lgtm

@mattzcarey
mattzcarey enabled auto-merge (squash) July 7, 2026 13:20
@mattzcarey
mattzcarey merged commit 0ab5d14 into mainJul 7, 2026
18 checks passed
@mattzcarey
mattzcarey deleted the fix/trim-standard-header-ows branch July 7, 2026 13:22
@claudeclaudeBot added the v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes label Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

server: trim HTTP OWS before validating standard MCP headers

3 participants

@mattzcarey@felixweinberger@github-advanced-security