Fix injection of server_id in internally-forwarded streamable HTTP requests#3024
Merged
crivetimihai merged 8 commits intomainfrom Feb 20, 2026
Merged
Fix injection of server_id in internally-forwarded streamable HTTP requests#3024crivetimihai merged 8 commits intomainfrom
crivetimihai merged 8 commits intomainfrom
Conversation
crivetimihai
previously approved these changes
Feb 19, 2026
Member
crivetimihai
left a comment
There was a problem hiding this comment.
Clean fix. The server_id injection from the URL path into JSON-RPC params is correctly guarded (if match and if server_id), and creates the params dict when missing. Comprehensive test coverage: injection with missing params, no-injection for non-server URLs, no-injection for notifications (202), and regression test for the local-owner path.
Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com>
Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com>
Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com>
Add test_forwarded_post_injects_server_id_with_existing_params to verify that server_id is properly merged into an existing params dict without overwriting other keys. Fix misleading docstring on related test. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…-owner branch The regex [a-fA-F0-9\-]+ guarantees server_id is always non-empty when match is truthy, making the `if server_id:` guard dead code. Removing it aligns the internally-forwarded branch with the structurally identical local-owner branch and eliminates a misleading code path where params dict creation would not be serialized back into body. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
ac5fb5d to
e580420
Compare
Replace `if "params" not in json_body` with `if not isinstance(... dict)` in both the internally-forwarded and local-owner injection branches. When params is null or a list (valid JSON-RPC but unusual for MCP), the key-existence check passes but dict subscript assignment throws TypeError, causing the request to silently fall through to the SDK path instead of routing to /rpc — defeating the server-scoped routing fix. Add parametrized tests for both branches covering params:null and params:[] to prevent regression. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
crivetimihai
approved these changes
Feb 20, 2026
Member
crivetimihai
left a comment
There was a problem hiding this comment.
Re-reviewed after the latest commits. The hardening looks good:
isinstance(json_body.get("params"), dict)correctly handlesnull,[], and missing params — all coerced to a fresh dict before injection. Better than the previous"params" not in json_bodycheck.- The fix is now applied to both injection paths (forwarded branch + local-owner branch) — good consistency.
- 7 new tests with solid coverage: missing params, existing params, non-dict params (null/list), no-match URL, notification, and regression for local-owner path.
LGTM.
vishu-bh
pushed a commit
that referenced
this pull request
Feb 24, 2026
…quests (#3024) * inject server_id for internally forwarded request Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> * added test cases for server_id injection Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> * lint Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> * lint Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> * increase cov Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> * fix(test): add missing test for server_id injection with existing params Add test_forwarded_post_injects_server_id_with_existing_params to verify that server_id is properly merged into an existing params dict without overwriting other keys. Fix misleading docstring on related test. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * refactor: remove redundant server_id guard for consistency with local-owner branch The regex [a-fA-F0-9\-]+ guarantees server_id is always non-empty when match is truthy, making the `if server_id:` guard dead code. Removing it aligns the internally-forwarded branch with the structurally identical local-owner branch and eliminates a misleading code path where params dict creation would not be serialized back into body. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix: guard server_id injection against non-dict params (null, list) Replace `if "params" not in json_body` with `if not isinstance(... dict)` in both the internally-forwarded and local-owner injection branches. When params is null or a list (valid JSON-RPC but unusual for MCP), the key-existence check passes but dict subscript assignment throws TypeError, causing the request to silently fall through to the SDK path instead of routing to /rpc — defeating the server-scoped routing fix. Add parametrized tests for both branches covering params:null and params:[] to prevent regression. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Keval Mahajan <mahajankeval23@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
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.
🐛 Bug-fix PR
Closes #3018
📌 Summary
When a Streamable HTTP POST request is forwarded between workers via the internal affinity mechanism (x-forwarded-internally=true), the server_id from the URL path (/servers/{id}/mcp) was not injected into the JSON-RPC params before posting to /rpc. This caused /rpc routing to fall into the unscoped list_tools path instead of the server-scoped list_server_tools path, breaking multi-tenant isolation in multi-worker deployments.
Why this matters: In production deployments with multiple workers and session affinity enabled, clients would receive tools from ALL servers instead of just the requested server, violating tenant isolation.
🔁 Reproduction Steps
Prerequisites:
Steps:
Expected: Only tools from the requested server
Actual: Tools from all servers (multi-tenant isolation broken)
🐞 Root Cause
The internally-forwarded branch (lines 1850-1873) was missing the server_id injection logic that was added to the local-owner branch in PR #2974.
Location: mcpgateway/transports/streamablehttp_transport.py:1850-1873
The match variable (containing the extracted server_id from the URL path) was available but not used in this branch.
💡 Fix Description
Added the same server_id injection logic to the internally-forwarded branch that already existed in the local-owner branch.
Key changes:
Inject server_id from URL path (lines 1861-1868):
Added comprehensive test coverage in test_streamablehttp_transport.py:
test_forwarded_post_injects_server_id_from_url - Verifies the fix
test_forwarded_post_no_server_id_in_url_no_injection - Edge case without server_id
test_forwarded_post_notification_no_server_id_injection - Notification handling
test_local_affinity_post_injects_server_id_regression - Regression test
Design points:
Mirrors the existing pattern in the local-owner branch (lines 2003-2010) for consistency
Uses the existing match variable captured at line 1776
Placed after notification handling (line 1859) and before httpx client call (line 1869)
Added debug logging for observability
All 251 transport tests pass with no regressions
Impact: Restores proper server-scoped routing and multi-tenant isolation in multi-worker deployments.
🧪 Verification
make lintmake testmake coverage📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)