fix(mcp): forwarded RPC non-2xx responses masked as success (#3365)#3371
Conversation
|
Hi @omorros, $ make lint-web flake8 bandit interrogate pylint verify
$ make autoflake isort black pre-commit
$ make doctest test htmlcovRegards, |
|
Hi these changes are needed to be incorporated for all the tests to pass. diff --git a/tests/unit/mcpgateway/services/test_mcp_session_pool_coverage.py b/tests/unit/mcpgateway/services/test_mcp_session_pool_coverage.py
index b1243307a..90a4d2a2f 100644
--- a/tests/unit/mcpgateway/services/test_mcp_session_pool_coverage.py
+++ b/tests/unit/mcpgateway/services/test_mcp_session_pool_coverage.py
@@ -1495,8 +1495,9 @@ class TestExecuteForwardedRequest:
pool = MCPSessionPool()
class DummyResponse:
- def __init__(self, data):
+ def __init__(self, data, status_code):
self._data = data
+ self.status_code = status_code
def json(self):
return self._data
@@ -1512,7 +1513,7 @@ class TestExecuteForwardedRequest:
self.post_calls.append({"url": url, "json": json, "headers": headers, "timeout": timeout})
return self._response
- dummy_client = DummyClient(DummyResponse({"jsonrpc": "2.0", "result": {"x": 1}, "id": 1}))
+ dummy_client = DummyClient(DummyResponse({"jsonrpc": "2.0", "result": {"x": 1}, "id": 1}, 200))
with patch("mcpgateway.services.mcp_session_pool.settings") as mock_settings:
mock_settings.port = 4444
@@ -1532,8 +1533,9 @@ class TestExecuteForwardedRequest:
pool = MCPSessionPool()
class DummyResponse:
- def __init__(self, data):
+ def __init__(self, data, status_code):
self._data = data
+ self.status_code = status_code
def json(self):
return self._data
@@ -1547,7 +1549,7 @@ class TestExecuteForwardedRequest:
async def post(self, *_args, **_kwargs):
return self._response
- dummy_client = DummyClient(DummyResponse({"jsonrpc": "2.0", "error": {"code": -32601, "message": "nope"}, "id": 1}))
+ dummy_client = DummyClient(DummyResponse({"jsonrpc": "2.0", "error": {"code": -32601, "message": "nope"}, "id": 1}, status_code=200))
with patch("mcpgateway.services.mcp_session_pool.settings") as mock_settings:
mock_settings.port = 4444 |
|
Hi @ja8zyjits, thanks for catching that and for providing the fix, I've applied your suggested changes and pushed the update. All tests are passing now. Let me know if there's anything else that needs to be done! ;) |
|
Hi @omorros , Regards, |
|
Hi @ja8zyjits, I've added unit tests covering all the new non-2xx response handling branches (JSON-RPC error propagation, non-JSON-RPC body mapping, and unparseable body fallback). All tests are passing. |
|
Hi @omorros , This looks good, Iam trying to reproduce output with and without the changes. BR |
|
Hi @ja8zyjits, Yes, I verified it locally. The unit and e2e tests call the fixed method directly with simulated 401, 403, and 502 responses, all pass and return proper errors. Before the fix, those would silently return {"result": {}}. A full Docker repro is tricky since you can't control which worker nginx routes to, but the tests hit the exact code path where the bug was. Let me know if anything else is needed! |
|
Hi @omorros , The following are the steps I carried out to reproduce the issue: In
|
|
Hi @ja8zyjits, I tested the forwarding pipeline locally with Redis and confirmed ownership registration, pub/sub forwarding, and the non-2xx error handling all work when the code paths are triggered. I agree it's hard to trigger end-to-end with gunicorn since it depends on which worker the internal /rpc call lands on. Happy to open a follow-up issue for improving integration test coverage on this area if that works for you. |
f85399c to
0ae28a5
Compare
…rror masking
_execute_forwarded_request() now inspects the response body before
returning a generic error on non-2xx HTTP status. If the body contains
a JSON-RPC error ({"error": {...}}), it is propagated directly. If
the body is non-JSON-RPC (e.g. {"detail": "Authorization token
required"}), the detail is included in the mapped JSON-RPC error
instead of being discarded. Unparseable bodies fall back to
response.text (truncated to 200 chars).
Closes IBM#3365
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Oriol Morros Vilaseca <OM368@student.aru.ac.uk>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…ON bodies Guard against response.json() returning None (JSON null) or a non-dict type, which would crash with TypeError at the `"error" in response_data` check. Also narrow exception catch from Exception to ValueError (the actual type raised by json parsing failures), consistent with codebase patterns. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
0ae28a5 to
7cabc5e
Compare
Maintainer rebase and reviewRebased onto current What was kept (the fix)
What was added during review
What was removedThe original PR included a large refactoring that reverted commit Test coverage (6 new tests)
Verification
Commits:
|
crivetimihai
left a comment
There was a problem hiding this comment.
Rebased, reviewed, hardened, and tested. The fix correctly addresses #3365 — forwarded RPC non-2xx responses now preserve error details instead of masking them. All tests pass, no regressions.
|
HI @omorros Regrads, |
🔗 Related Issue
Closes #3365
📝 Summary
_execute_forwarded_request() now gates on HTTP status code before parsing the response body. Non-2xx
responses with non-JSON-RPC bodies (e.g. 401 {"detail":"..."}) are mapped to JSON-RPC errors instead of
silently returning {"result": {}}. The two session-affinity tests that depended on localhost port state are
now hermetic via mocked HTTP responses, and two new test cases cover the non-2xx error mapping paths.
🏷️ Type of Change
🧪 Verification
make lintmake testmake coverage✅ Checklist
make black isort pre-commit)📓 Notes (optional)
Files changed:
_execute_forwarded_request()
test_execute_forwarded_request_returns_error_on_non_2xx_non_jsonrpc and
test_execute_forwarded_request_propagates_jsonrpc_error_on_non_2xx