Skip to main content
Glama

search_activities_tool

Find Strava activities using filters like type, date range, distance, or search terms to locate specific workouts in your fitness history.

Instructions

Search activities with optional filters. Note: Name/description search is client-side, so a reasonable limit is recommended.

Args: query: Search term to match in activity name (case-insensitive, partial match) activity_type: Filter by activity type (e.g., "Run", "Ride", "Walk", "Hike") after: ISO 8601 date string (e.g., "2025-01-01") - activities after this date before: ISO 8601 date string (e.g., "2026-01-01") - activities before this date min_distance: Minimum distance in meters max_distance: Maximum distance in meters limit: Maximum number of activities to fetch from API (default 50)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNo
activity_typeNo
afterNo
beforeNo
min_distanceNo
max_distanceNo
limitNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • server.py:54-91 (handler)
    MCP tool handler for search_activities_tool. Decorated with @mcp.tool(), this function accepts query parameters (query, activity_type, after, before, min_distance, max_distance, limit), applies MAX_LIMIT clamping, calls the search_activities service function, and returns a list of dictionaries representing activity summaries.
    @mcp.tool()
    def search_activities_tool(
        query: Optional[str] = None,
        activity_type: Optional[str] = None,
        after: Optional[str] = None,
        before: Optional[str] = None,
        min_distance: Optional[float] = None,
        max_distance: Optional[float] = None,
        limit: int = 50,
    ) -> list[dict]:
        """
        Search activities with optional filters.
        Note: Name/description search is client-side, so a reasonable limit is recommended.
    
        Args:
            query: Search term to match in activity name (case-insensitive, partial match)
            activity_type: Filter by activity type (e.g., "Run", "Ride", "Walk", "Hike")
            after: ISO 8601 date string (e.g., "2025-01-01") - activities after this date
            before: ISO 8601 date string (e.g., "2026-01-01") - activities before this date
            min_distance: Minimum distance in meters
            max_distance: Maximum distance in meters
            limit: Maximum number of activities to fetch from API (default 50)
        """
        if limit > MAX_LIMIT:
            limit = MAX_LIMIT
    
        client = get_client()
        activities = search_activities(
            client,
            query=query,
            activity_type=activity_type,
            after=after,
            before=before,
            min_distance=min_distance,
            max_distance=max_distance,
            limit=limit,
        )
        return [activity.to_dict() for activity in activities]
  • Core service function that implements the search logic. Parses ISO 8601 date strings, fetches activities from Strava API using client.get_activities(), then applies client-side filters for query (case-insensitive name matching), activity_type, and distance ranges. Returns a list of ActivitySummary objects.
    def search_activities(
        client: Client,
        query: Optional[str] = None,
        activity_type: Optional[str] = None,
        after: Optional[str] = None,
        before: Optional[str] = None,
        min_distance: Optional[float] = None,
        max_distance: Optional[float] = None,
        limit: int = 50,
    ) -> list[ActivitySummary]:
        """Search activities with optional filters."""
        # Parse date strings if provided
        after_date = None
        before_date = None
    
        if after:
            try:
                after_date = datetime.datetime.fromisoformat(after.replace("Z", "+00:00"))
            except ValueError:
                sys.stderr.write(f"Warning: Invalid 'after' date format: {after}\n")
    
        if before:
            try:
                before_date = datetime.datetime.fromisoformat(before.replace("Z", "+00:00"))
            except ValueError:
                sys.stderr.write(f"Warning: Invalid 'before' date format: {before}\n")
    
        # Fetch activities with date filters
        activities = client.get_activities(
            before=before_date, after=after_date, limit=limit
        )
    
        result = []
        query_lower = query.lower() if query else None
        type_lower = activity_type.lower() if activity_type else None
    
        for activity in activities:
            # Handle moving_time safely
            moving_time = (
                getattr(activity.moving_time, "seconds", 0) if activity.moving_time else 0
            )
    
            # Get activity details
            activity_name = activity.name or ""
            activity_type_str = str(activity.type)
            activity_distance = float(activity.distance) if activity.distance else 0.0
    
            # Apply filters
            if query_lower and query_lower not in activity_name.lower():
                continue
    
            if type_lower and type_lower not in activity_type_str.lower():
                continue
    
            if min_distance is not None and activity_distance < min_distance:
                continue
    
            if max_distance is not None and activity_distance > max_distance:
                continue
    
            summary = ActivitySummary(
                id=activity.id or 0,
                name=activity_name,
                type=activity_type_str,
                start_date=activity.start_date.isoformat() if activity.start_date else None,
                distance=activity_distance,
                moving_time=moving_time,
                total_elevation_gain=float(activity.total_elevation_gain)
                if activity.total_elevation_gain
                else 0.0,
            )
            result.append(summary)
    
        return result
  • Dataclass schema defining the ActivitySummary model with fields: id, name, type, start_date, distance, moving_time, total_elevation_gain, average_speed, and max_speed. Includes a to_dict() method for serialization.
    @dataclass
    class ActivitySummary:
        """Summary of a Strava activity."""
    
        id: int
        name: str
        type: str
        start_date: Optional[str]
        distance: float
        moving_time: int
        total_elevation_gain: float
        average_speed: float = 0.0
        max_speed: float = 0.0
    
        def to_dict(self) -> dict:
            """Convert to dictionary for serialization."""
            return asdict(self)
  • Constant MAX_LIMIT = 200 used to clamp the limit parameter in search_activities_tool to prevent excessive API requests.
    MAX_LIMIT = 200
  • Test case that verifies the search_activities_tool properly clamps excessive limit values to MAX_LIMIT. Shows how the tool is imported and accessed via the .fn attribute.
    @patch("server.get_client")
    @patch("server.search_activities")
    def test_search_activities_tool_limit_clamping(mock_search_activities, mock_get_client):
        # Setup
        mock_client = MagicMock()
        mock_get_client.return_value = mock_client
        mock_search_activities.return_value = []
    
        # Test with limit > MAX_LIMIT
        excessive_limit = MAX_LIMIT + 100
        search_activities_tool.fn(limit=excessive_limit)
    
        # Verify search_activities was called with MAX_LIMIT (among other args)
        # Check that limit=MAX_LIMIT is in kwargs or args
        call_args = mock_search_activities.call_args
        assert call_args.kwargs["limit"] == MAX_LIMIT
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and adds valuable behavioral context: it discloses that name/description search is client-side (implying performance considerations), recommends a reasonable limit, specifies case-insensitive partial matching for queries, and provides default values and examples. This goes beyond basic functionality to include implementation details and constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with a brief overview followed by detailed parameter explanations. Every sentence earns its place by providing necessary information. It could be slightly more front-loaded with a clearer distinction from siblings, but overall it's efficient and organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 7 parameters, 0% schema coverage, no annotations, but an output schema, the description is quite complete: it covers all parameters, behavioral constraints, and usage notes. The presence of an output schema means return values don't need explanation. It adequately addresses the complexity, though could benefit from more explicit sibling differentiation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Given 0% schema description coverage, the description fully compensates by explaining all 7 parameters in detail: it clarifies that 'query' uses case-insensitive partial matching, provides examples for 'activity_type', specifies ISO 8601 format for date parameters, explains units for distance parameters, and states the default for 'limit'. This adds essential meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches activities with optional filters, providing a specific verb ('search') and resource ('activities'). It distinguishes from siblings like 'list_activities_tool' by emphasizing search capabilities, though it doesn't explicitly contrast them. The purpose is unambiguous but lacks explicit sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides implied usage guidance through the note about client-side search and recommendation for a reasonable limit, suggesting when to use caution. However, it doesn't explicitly state when to use this tool versus alternatives like 'list_activities_tool' or other siblings, nor does it provide clear exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/saxenanurag/strava-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server