From ccfdbbaa86560f4d5666b770001a191aeb0ebb42 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Sun, 17 May 2026 19:50:36 +0200 Subject: [PATCH] feat: add get_steps and get_daily_stats tools --- server.py | 34 ++++++++++++++++++++++++++++++++++ tests/test_server.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/server.py b/server.py index 7a93cc3..44f39cc 100644 --- a/server.py +++ b/server.py @@ -232,6 +232,40 @@ def get_spo2(date: str = "") -> str: return f"Error fetching SpO2 data: {exc}" +@mcp.tool() +def get_steps(date: str = "") -> str: + """Get step count and daily goal for a date (YYYY-MM-DD). Defaults to today.""" + if err := _check_auth(): + return err + cdate = date or _today() + try: + result = _client.get_steps_data(cdate) + if not result: + return f"No steps data found for {cdate}" + return json.dumps(result, indent=2) + except GarminConnectAuthenticationError: + return "Authentication error. Call authenticate() again." + except Exception as exc: + return f"Error fetching steps data: {exc}" + + +@mcp.tool() +def get_daily_stats(date: str = "") -> str: + """Get daily summary for a date (YYYY-MM-DD): calories, floors, intensity minutes. Defaults to today.""" + if err := _check_auth(): + return err + cdate = date or _today() + try: + result = _client.get_stats(cdate) + if not result: + return f"No daily stats found for {cdate}" + return json.dumps(result, indent=2) + except GarminConnectAuthenticationError: + return "Authentication error. Call authenticate() again." + except Exception as exc: + return f"Error fetching daily stats: {exc}" + + if __name__ == "__main__": _startup_login() mcp.run() diff --git a/tests/test_server.py b/tests/test_server.py index b3e6d60..e8204d8 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -212,3 +212,38 @@ def test_health_tools_unauthenticated(): for fn in (server.get_sleep, server.get_heart_rate, server.get_stress, server.get_body_battery, server.get_hrv, server.get_spo2): assert "Not authenticated" in fn("2026-05-17") + + +def test_get_steps_success(): + server._auth_state = "authenticated" + server._client = MagicMock() + server._client.get_steps_data.return_value = [{"steps": 8342, "primaryActivityLevel": "active"}] + data = json.loads(server.get_steps("2026-05-17")) + assert data[0]["steps"] == 8342 + server._client.get_steps_data.assert_called_once_with("2026-05-17") + + +def test_get_steps_no_data(): + server._auth_state = "authenticated" + server._client = MagicMock() + server._client.get_steps_data.return_value = [] + assert server.get_steps("2026-05-17") == "No steps data found for 2026-05-17" + + +def test_get_daily_stats_success(): + server._auth_state = "authenticated" + server._client = MagicMock() + server._client.get_stats.return_value = { + "totalSteps": 9231, + "totalKilocalories": 2100.0, + "floorsAscended": 5, + } + data = json.loads(server.get_daily_stats("2026-05-17")) + assert data["totalSteps"] == 9231 + server._client.get_stats.assert_called_once_with("2026-05-17") + + +def test_steps_and_stats_unauthenticated(): + server._auth_state = "unauthenticated" + for fn in (server.get_steps, server.get_daily_stats): + assert "Not authenticated" in fn("2026-05-17")