feat: add get_activities and get_activity_details tools

This commit is contained in:
Christophe Vila
2026-05-17 19:46:45 +02:00
parent 6b38f17e9b
commit 3630aa2c42
2 changed files with 91 additions and 0 deletions

View File

@@ -100,6 +100,36 @@ def complete_mfa(code: str) -> str:
return "Timed out waiting for authentication to complete."
@mcp.tool()
def get_activities(start_date: str = "", end_date: str = "", limit: int = 20) -> str:
"""List Garmin activities between two dates (YYYY-MM-DD). Defaults to today."""
if err := _check_auth():
return err
start = start_date or _today()
end = end_date or _today()
try:
result = _client.get_activities_by_date(start, end)
return json.dumps(result[:limit], indent=2)
except GarminConnectAuthenticationError:
return "Authentication error. Call authenticate() again."
except Exception as exc:
return f"Error fetching activities: {exc}"
@mcp.tool()
def get_activity_details(activity_id: str) -> str:
"""Get full details for a single activity by its ID (splits, laps, metrics)."""
if err := _check_auth():
return err
try:
result = _client.get_activity_details(activity_id)
return json.dumps(result, indent=2)
except GarminConnectAuthenticationError:
return "Authentication error. Call authenticate() again."
except Exception as exc:
return f"Error fetching activity details: {exc}"
if __name__ == "__main__":
_startup_login()
mcp.run()

View File

@@ -2,9 +2,11 @@ import json
import os
import queue
import threading
from datetime import date
from unittest.mock import MagicMock, patch
import pytest
from garminconnect import GarminConnectAuthenticationError
import server
@@ -84,3 +86,62 @@ def test_authenticate_missing_credentials():
os.environ.pop("GARMIN_PASSWORD", None)
result = server.authenticate()
assert result == "GARMIN_EMAIL and GARMIN_PASSWORD environment variables are required."
def test_get_activities_unauthenticated():
server._auth_state = "unauthenticated"
assert "Not authenticated" in server.get_activities()
def test_get_activities_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = [
{"activityId": "111", "activityType": {"typeKey": "running"}, "distance": 5000.0},
{"activityId": "222", "activityType": {"typeKey": "cycling"}, "distance": 20000.0},
]
result = server.get_activities("2026-05-01", "2026-05-17", 20)
data = json.loads(result)
assert len(data) == 2
assert data[0]["activityId"] == "111"
server._client.get_activities_by_date.assert_called_once_with("2026-05-01", "2026-05-17")
def test_get_activities_limit():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = [{"activityId": str(i)} for i in range(10)]
data = json.loads(server.get_activities("2026-05-01", "2026-05-17", 3))
assert len(data) == 3
def test_get_activities_defaults_to_today():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.return_value = []
server.get_activities()
today = date.today().isoformat()
server._client.get_activities_by_date.assert_called_once_with(today, today)
def test_get_activities_auth_error():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activities_by_date.side_effect = GarminConnectAuthenticationError("auth")
assert "Authentication error" in server.get_activities()
def test_get_activity_details_success():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activity_details.return_value = {"activityId": "111", "laps": []}
data = json.loads(server.get_activity_details("111"))
assert data["activityId"] == "111"
server._client.get_activity_details.assert_called_once_with("111")
def test_get_activity_details_error():
server._auth_state = "authenticated"
server._client = MagicMock()
server._client.get_activity_details.side_effect = Exception("not found")
assert "Error fetching activity details" in server.get_activity_details("999")