Skip to main content
Glama
Pranav-Karra-3301

CATA Bus MCP Server

next_arrivals_tool

Check upcoming bus arrivals at a specific CATA stop by entering the stop ID and optional time horizon.

Instructions

Get next arrivals at a specific stop.

Args: stop_id: The stop ID to query horizon_minutes: How many minutes ahead to look (default 30)

Returns: List of upcoming arrivals with trip ID, route ID, arrival time, and delay

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stop_idYes
horizon_minutesNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The @mcp.tool decorated handler function that defines the tool interface, parameters, docstring (serving as schema), and delegates to the core logic in next_arrivals helper.
    @mcp.tool
    async def next_arrivals_tool(stop_id: str, horizon_minutes: int = 30) -> list[dict[str, Any]]:
        """Get next arrivals at a specific stop.
    
        Args:
            stop_id: The stop ID to query
            horizon_minutes: How many minutes ahead to look (default 30)
    
        Returns:
            List of upcoming arrivals with trip ID, route ID, arrival time, and delay
        """
        await ensure_initialized()
        if not gtfs_data or not gtfs_data.stop_times:
            return []
        return await next_arrivals(gtfs_data, realtime_poller.data, stop_id, horizon_minutes)
  • The core helper function implementing the next arrivals logic: parses scheduled stop times from GTFS static data, applies realtime updates/delays, filters by time horizon, and returns sorted list of arrivals.
    async def next_arrivals(
        gtfs_data: GTFSData, realtime_data: RealtimeData, stop_id: str, horizon_minutes: int = 30
    ) -> list[dict[str, Any]]:
        """
        Get next arrivals at a specific stop.
    
        Args:
            gtfs_data: The GTFS static data.
            realtime_data: The GTFS realtime data.
            stop_id: The stop ID to query.
            horizon_minutes: How many minutes ahead to look (default 30).
    
        Returns:
            List of upcoming arrivals with trip ID, route ID, arrival time, and delay.
        """
        # Get current time in Eastern timezone (CATA operates in ET)
        eastern = pytz.timezone("America/New_York")
        now = datetime.now(eastern)
        horizon = now + timedelta(minutes=horizon_minutes)
    
        arrivals = []
    
        # First, get scheduled arrivals from static data
        scheduled = {}
        for stop_time in gtfs_data.stop_times:
            if stop_time.stop_id != stop_id:
                continue
    
            # Parse arrival time
            arrival_time, days_offset = parse_gtfs_time(stop_time.arrival_time)
            # Must use localize() with pytz timezones, not datetime.combine(..., tzinfo)
            scheduled_datetime = eastern.localize(
                datetime.combine(now.date() + timedelta(days=days_offset), arrival_time)
            )
    
            # Check if within horizon
            if now <= scheduled_datetime <= horizon:
                trip = gtfs_data.trips.get(stop_time.trip_id)
                if trip:
                    scheduled[stop_time.trip_id] = {
                        "trip_id": stop_time.trip_id,
                        "route_id": trip.route_id,
                        "scheduled_arrival": scheduled_datetime,
                        "stop_sequence": stop_time.stop_sequence,
                    }
    
        # Apply realtime updates
        for trip_id, scheduled_info in scheduled.items():
            arrival_info = {
                "trip_id": trip_id,
                "route_id": scheduled_info["route_id"],
                "arrival_time_iso": scheduled_info["scheduled_arrival"].isoformat(),
                "delay_sec": 0,
            }
    
            # Check for realtime updates
            if trip_id in realtime_data.trip_updates:
                trip_update = realtime_data.trip_updates[trip_id]
    
                # Find the stop time update for this stop
                for stu in trip_update.stop_time_updates:
                    if stu.get("stop_id") == stop_id:
                        if "arrival_delay" in stu:
                            arrival_info["delay_sec"] = stu["arrival_delay"]
                            # Adjust arrival time with delay
                            adjusted_arrival = scheduled_info["scheduled_arrival"] + timedelta(
                                seconds=stu["arrival_delay"]
                            )
                            arrival_info["arrival_time_iso"] = adjusted_arrival.isoformat()
                        elif "arrival_time" in stu and stu["arrival_time"]:
                            # Use absolute arrival time if provided
                            arrival_dt = datetime.fromtimestamp(stu["arrival_time"], tz=UTC)
                            arrival_info["arrival_time_iso"] = arrival_dt.isoformat()
                            # Calculate delay
                            arrival_info["delay_sec"] = int(
                                (arrival_dt - scheduled_info["scheduled_arrival"]).total_seconds()
                            )
                        break
    
            arrivals.append(arrival_info)
    
        # Sort by arrival time
        arrivals.sort(key=lambda x: x["arrival_time_iso"])
    
        return arrivals
  • Utility helper to parse GTFS time strings that may span multiple days (e.g., 25:30:00).
    def parse_gtfs_time(time_str: str) -> tuple[time, int]:
        """Parse GTFS time format (can be > 24:00:00 for next day).
    
        Returns:
            A tuple of (time object, days_offset) where days_offset is 0 or more
            for times that extend past midnight.
        """
        parts = time_str.split(":")
        hours = int(parts[0])
        minutes = int(parts[1])
        seconds = int(parts[2]) if len(parts) > 2 else 0
    
        # Handle times after midnight (e.g., 25:30:00)
        days_offset = hours // 24
        hours = hours % 24
    
        return time(hours, minutes, seconds), days_offset
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the return format but doesn't cover critical aspects like error handling, rate limits, authentication needs, data freshness, or whether this is a read-only operation. For a tool with zero annotation coverage, this leaves significant gaps in understanding its behavior.

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

Conciseness5/5

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

The description is perfectly structured and front-loaded with the core purpose, followed by organized sections for Args and Returns. Every sentence earns its place with zero wasted words, making it highly efficient and easy to parse.

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

Completeness3/5

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

Given the tool's moderate complexity, no annotations, and the presence of an output schema (which handles return value documentation), the description is adequate but incomplete. It covers parameters well but lacks behavioral context and usage guidelines, making it minimally viable but with clear gaps.

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

Parameters4/5

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

The description adds meaningful context beyond the input schema, which has 0% description coverage. It explains that stop_id is 'The stop ID to query' and horizon_minutes defines 'How many minutes ahead to look (default 30)', providing clear semantic meaning that compensates for the schema's lack of descriptions.

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

Purpose5/5

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

The description clearly states the specific action ('Get next arrivals') and resource ('at a specific stop'), distinguishing it from sibling tools like list_routes_tool or search_stops_tool. It precisely defines what the tool does without being vague or tautological.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives like trip_alerts_tool or vehicle_positions_tool. It lacks context about prerequisites, exclusions, or typical use cases, offering only basic functional information without comparative guidance.

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/Pranav-Karra-3301/catabus-mcp'

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