') + ')', '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: validate and forward SEP-2243 parameter headers by lucarlig · Pull Request #109 · contextforge-org/contextforge-data-plane · GitHub
Skip to content

fix: validate and forward SEP-2243 parameter headers - #109

Merged
lucarlig merged 18 commits into
mainfrom
user/luca/sep2243-rmcp-headers
Aug 31, 2026
Merged

fix: validate and forward SEP-2243 parameter headers#109
lucarlig merged 18 commits into
mainfrom
user/luca/sep2243-rmcp-headers

Conversation

@lucarlig

@lucarliglucarlig commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • validate modern tools/callMcp-Param-* headers against request-scoped tool schemas published by the control plane
  • return JSON-RPC -32020 for a mismatch or missing published schema
  • forward validated parameter headers unchanged on the outgoing tools/call and protect them from backend header configuration
  • keep plugin handling unchanged: plugins run after validation and do not cause header reconstruction

The dataplane does not call tools/list, reconstruct headers from arguments, or change RMCP. This PR requires IBM/mcp-context-forge#6348 to publish visibility-filtered schemas keyed by original upstream tool name.

The GitHub composed conformance job still pulls the stock control-plane image and therefore fails to deserialize its pre-#6348 payload (missing field tool_schemas). No dataplane fallback is included; the job will pass once #6348 is merged into that image.

Stacked on #107. Related: IBM/mcp-context-forge#6256.

Verification

  • cargo clippy --locked --workspace --all-targets -- -D warnings
  • cargo nextest run --locked --workspace (227 passed, 3 skipped)
  • workspace build, dependency-policy, shear, formatting, generated-schema, and wiki checks
  • conformance adapter, reporter, and baseline unit tests
  • control-plane publisher unit tests (26 passed), Ruff, and Black
  • cross-repo E2E with the official conformance fixture and #6348 publisher: matching plain/Base64 values pass; mismatched, invalid Base64, and missing headers return JSON-RPC -32020

The direct fixture E2E is used because the official server scenario currently reaches the separately baselined stateless tools/list session limitation during setup. Client findings remain limited to the existing four shell-adapter generation IDs.

@lucarliglucarlig changed the title fix: generate SEP-2243 parameter headersfix: require stateless metadata and generate SEP-2243 headersAug 21, 2026
@lucarliglucarlig changed the title fix: require stateless metadata and generate SEP-2243 headersfix: validate and generate SEP-2243 headersAug 21, 2026
@lucarliglucarlig changed the title fix: validate and generate SEP-2243 headersfix: validate and forward SEP-2243 headersAug 21, 2026
@lucarliglucarlig changed the title fix: validate and forward SEP-2243 headersfix: forward SEP-2243 parameter headersAug 21, 2026
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from 4005c69 to e3abd7eCompareAugust 21, 2026 16:42
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from 374730d to 7a65618CompareAugust 24, 2026 08:53
@lucarliglucarlig changed the title fix: forward SEP-2243 parameter headersfix: validate and forward SEP-2243 parameter headersAug 24, 2026
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from 7a65618 to 752a212CompareAugust 24, 2026 13:59
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from 752a212 to 0a81ed1CompareAugust 24, 2026 14:29
@lucarlig
lucarlig marked this pull request as ready for review August 25, 2026 12:33
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from a203fcb to d23977dCompareAugust 26, 2026 08:33
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from d23977d to 618c702CompareAugust 26, 2026 09:07

@dawid-nowakdawid-nowak 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.

  1. I don't see how checking the max body size of the message is a part of header validation logic. Ideally, body size validation should be in a separate layer and re-using layers provided by Axum.

  2. This header validation logic only applies to tools. Not sure if using a layer approach is valid as this is going to be executed on all rmcp calls as well as new and old protocols.

Comment threadcrates/contextforge-data-plane-lib/src/layers/mcp_param_validation.rs Outdated
Comment threadcrates/contextforge-data-plane-lib/src/layers/mcp_param_validation.rs Outdated
Comment threadcrates/contextforge-data-plane-lib/src/layers/mcp_param_validation.rs Outdated
@lucarlig
lucarligforce-pushed the user/luca/sep2243-rmcp-headers branch from 403863c to 0c47a08CompareAugust 26, 2026 10:41

@dawid-nowakdawid-nowak 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.

I think you should move validation to tools/call handler.

  1. If something is not right with the request rmcp will validate it anyway.
  2. You can easily get access to all necessary headers via:
    let downstream_headers = cx.extensions.get::().map(|parts| &parts.headers);
  3. Everything related to tool call is in one place.
  4. Not sure if this header is auto propagated to upstream so you might need to do it manually.

@lucarlig

lucarlig commented Aug 26, 2026

Copy link
Copy Markdown
ContributorAuthor

Addressed in ef71cba:

  • moved SEP-2243 parameter validation into the tools/call handler
  • read downstream headers from RMCP request-context Parts
  • removed the parameter-validation middleware and duplicate Axum body-limit handling; RMCP owns envelope/body validation
  • kept explicit transparent Mcp-Param-* forwarding in upstream transport setup
  • covered modern mismatch/missing-schema rejection and the legacy-protocol bypass

Base automatically changed from user/luca/add-client-conformance to mainAugust 27, 2026 13:24
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
@dawid-nowak
dawid-nowakforce-pushed the user/luca/sep2243-rmcp-headers branch from ef71cba to f93e4c5CompareAugust 27, 2026 13:24
dawid-nowak
dawid-nowak previously approved these changes Aug 28, 2026
Signed-off-by: lucarlig <luca.carlig@ibm.com>
@lucarlig
lucarlig merged commit 3f05f4c into mainAug 31, 2026
11 of 12 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@lucarlig@dawid-nowak