Skip to content

MCP tools return "Internal MCP tool error." when OpenCRE is unreachable or times out #1163

Open

Description

@ShaanGS

Issue

What is the issue?

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

  1. With nothing running on port 5999, start the server: OPENCRE_BASE_URL=http://127.0.0.1:5999 python -m application.mcp
  2. Call list_standards with {}. You get Internal MCP tool error. right away.
  3. For the timeout, start it with OPENCRE_BASE_URL=https://opencre.org and call get_documents_by_tag with {"tag": ["family:standard"]}. Same message after about 30 s. (That query is slow on opencre.org right now because of /rest/v1/tags and db.get_by_tags() return unbounded results — pagination TODO never implemented #1087, so it's an easy way to trigger the timeout.)

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.

Activity

  1. saxamkeshvani123456-maker commented on Oct 21, 2026

    @saxamkeshvani123456-maker

    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?

  2. ShaanGS commented on Oct 21, 2026

    @ShaanGS
    Author

    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.

  3. northdpole commented on Oct 21, 2026

    @northdpole
    Collaborator

    pinging @Pa04rth , @PRAteek-singHWY do you think it makes sense? I think it does

  4. PRAteek-singHWY commented on Oct 21, 2026

    @PRAteek-singHWY
    Contributor

    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

  5. ShaanGS commented on Oct 21, 2026

    @ShaanGS
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions