Skip to content

_GarminProxy mislabels all API errors as "unreachable" and discards the real Garmin message #183

Description

@jontyms

Summary

_GarminProxy in src/garmin_mcp/__init__.py collapses every GarminConnectConnectionError into the canned string "Garmin Connect is unreachable. Check your network connection or try again later." and discards the original exception detail. With garminconnect==0.3.2, GarminConnectConnectionError is the catch-all raised for all non-2xx responses (400/404/500), not just genuine connectivity failures — so ordinary client errors are reported to the MCP client as a network outage, and the actionable message is lost.

Where

# src/garmin_mcp/__init__.py
_MESSAGES = {
    ...
    GarminConnectConnectionError: (
        "Garmin Connect is unreachable. Check your network connection or try again later."
    ),
}
...
except tuple(self._MESSAGES) as exc:
    for exc_type, msg in self._MESSAGES.items():
        if isinstance(exc, exc_type):
            raise type(exc)(msg) from None   # <-- replaces message, drops str(exc)
    raise

Reproduction

Call get_activities_by_date with an activity sub-type (e.g. strength_training) over a date range:

get_activities_by_date(start_date="2025-11-01", end_date="2025-12-31", activity_type="strength_training")

Garmin returns:

GarminConnectConnectionError: API Error 400 - Activity type cannot be an activity sub type

but the tool reports:

Error retrieving activities by date: Garmin Connect is unreachable. Check your network connection or try again later.

The user is told the network is down when in fact they passed an unsupported filter value. The from None also suppresses the traceback chain, so the real cause is unrecoverable from the response.

Impact

Any 4xx/5xx from Garmin (bad filter, not-found, server-side error) is indistinguishable from an outage. This makes tool failures very hard to debug from the MCP client side.

Suggested fix

Preserve the underlying detail (and reword the connection message, since it is not always "unreachable"):

GarminConnectConnectionError: "Garmin Connect request failed",
...
detail = str(exc).strip()
raise type(exc)(f"{msg}: {detail}" if detail else msg) from None

which yields e.g. Garmin Connect request failed: API Error 400 - Activity type cannot be an activity sub type. Auth-expired and rate-limit messages can keep their tailored text but would likewise benefit from appending the detail.

Environment

  • garminconnect 0.3.2
  • streamable-http transport

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions