You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MCP tools return "Internal MCP tool error." when OpenCRE is unreachable or times out #1163
I hit this while trying the MCP server against opencre.org. When the server can't connect to OpenCRE, or a REST call takes longer than the 30 s timeout, the tool result is just Internal MCP tool error.. The actual error is only in the server's stderr, which MCP clients usually don't show. So from the client side you can't tell a server that isn't running, a wrong OPENCRE_BASE_URL or a slow endpoint apart from a real bug in the MCP server.
It looks like RestClient._send() in application/mcp/rest_client.py doesn't catch requests exceptions, so a ConnectionError or ReadTimeout skips the RestClientError handlers in on_call_tool (application/mcp/server.py) and ends up in the last except Exception, which is meant for unexpected errors.
Expected Behaviour
A tool error that says what went wrong, something like:
Could not reach OpenCRE at http://127.0.0.1:5999 (ConnectionError)
OpenCRE at https://opencre.org timed out after 30s
and Internal MCP tool error. only for actual bugs.
Actual Behaviour
is_error: true with Internal MCP tool error. in both cases. The real exceptions are in the server log:
requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=5999): Max retries exceeded with url: /rest/v1/standards (Caused by NewConnectionError(... Failed to establish a new connection: [Errno 61] Connection refused"))
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='opencre.org', port=443): Read timed out. (read timeout=30.0)
Steps to reproduce
With nothing running on port 5999, start the server: OPENCRE_BASE_URL=http://127.0.0.1:5999 python -m application.mcp
Call list_standards with {}. You get Internal MCP tool error. right away.
Tested on main (06f2c56) with Python 3.11. I called the tools from a small script using the MCP SDK's stdio_client.
Possible fix: catch requests.Timeout / requests.RequestException around the _send() call in call_spec() and raise a RestClientError with a message like the ones above, passed through _scrub() like the REST error text. server.py already returns RestClientError messages to the client and logs the traceback, so nothing needs to change there. I have this working locally with tests and can open a PR if that sounds good.
Reactions are currently unavailable
Activity
saxamkeshvani123456-maker commented on Oct 21, 2026
Hi! I’m interested in contributing to this issue. I noticed you mentioned having a local fix. Have you already started a PR, or would this issue still be available for another contributor to work on?
Hi @saxamkeshvani123456-maker, thanks, but I'm already on this one. The fix and tests are ready locally, I'm just waiting for a maintainer to confirm the approach before I open the PR.
Looks good to me, I was able to reproduce the issue on main (06f2c56) using the SDK’s stdio client against a dead port, and the diagnosis lines up with the current code. Wrapping the connection and timeout errors in RestClientError seems like the right fix, and it looks like server.py already handles these errors appropriately.
Just two small suggestions for the PR:
Catch requests.Timeout before the general requests.RequestException so we can preserve distinct error messages.
Pass the error message through _scrub() as well, to ensure no sensitive tokens are exposed to the client.
For the test, injecting a session whose request() raises requests.ConnectionError should be a straightforward way to cover this case, since the Flask test session doesn’t raise these errors naturally.
Otherwise, this looks good from my side. Thanks for digging into this @ShaanGS.
Once @northdpole approves this, u can go ahead with the fix.
Thanks @PRAteek-singHWY! I'd already opened #1165 after northdpole's reply, and it covers both points: requests.Timeout is checked before the other requests errors, and the message goes through _scrub(). For the tests I used a fake session whose request() raises the different requests errors, plus one test with a real session against a closed local port. Happy to adjust it if northdpole wants anything changed.
Activity
saxamkeshvani123456-maker commentedon Oct 21, 2026
Hi! I’m interested in contributing to this issue. I noticed you mentioned having a local fix. Have you already started a PR, or would this issue still be available for another contributor to work on?
ShaanGS commentedon Oct 21, 2026
Hi @saxamkeshvani123456-maker, thanks, but I'm already on this one. The fix and tests are ready locally, I'm just waiting for a maintainer to confirm the approach before I open the PR.
northdpole commentedon Oct 21, 2026
pinging @Pa04rth , @PRAteek-singHWY do you think it makes sense? I think it does
PRAteek-singHWY commentedon Oct 21, 2026
Looks good to me, I was able to reproduce the issue on main (06f2c56) using the SDK’s stdio client against a dead port, and the diagnosis lines up with the current code. Wrapping the connection and timeout errors in RestClientError seems like the right fix, and it looks like server.py already handles these errors appropriately.
Just two small suggestions for the PR:
Catch requests.Timeout before the general requests.RequestException so we can preserve distinct error messages.
Pass the error message through _scrub() as well, to ensure no sensitive tokens are exposed to the client.
For the test, injecting a session whose request() raises requests.ConnectionError should be a straightforward way to cover this case, since the Flask test session doesn’t raise these errors naturally.
Otherwise, this looks good from my side. Thanks for digging into this @ShaanGS.
Once @northdpole approves this, u can go ahead with the fix.
Great work.
Thank you
ShaanGS commentedon Oct 21, 2026
Thanks @PRAteek-singHWY! I'd already opened #1165 after northdpole's reply, and it covers both points:
requests.Timeoutis checked before the otherrequestserrors, and the message goes through_scrub(). For the tests I used a fake session whoserequest()raises the differentrequestserrors, plus one test with a real session against a closed local port. Happy to adjust it if northdpole wants anything changed.