Skip to main content
Glama
countaight

BusTime MCP

by countaight
README.md
# BusTime MCP

An [MCP](https://modelcontextprotocol.io) server that wraps the [CTA (Chicago Transit Authority) Bus Tracker API](https://www.transitchicago.com/developers/bustracker/), giving MCP clients live access to CTA bus routes, stops, predictions, vehicle locations, patterns, and detours. Built with [FastMCP](https://gofastmcp.com) and deployed via [Prefect Horizon](https://gofastmcp.com/deployment/prefect-horizon).

## Features

- Full coverage of the CTA Bus Tracker v3 API (routes, directions, stops, patterns, predictions, vehicles, locales, detours, enhanced detours, system time)
- Async HTTP via `httpx`, with clean error handling for missing config, bad status codes, and network failures
- In-memory TTL caching on slow-changing endpoints (routes, directions, stops, patterns, locales, detours) — real-time endpoints (predictions, vehicles, time) always hit the live API
- Server-level `instructions` and per-tool descriptions designed to help MCP clients chain calls correctly (e.g. routes → directions → stops → predictions)
- Test suite covering the HTTP layer, caching behavior, and tool parameter validation

## Prerequisites

- A CTA Bus Tracker developer API key
- A GitHub account, with this repo pushed to it (Horizon deploys from a GitHub repo)
- A [Horizon](https://horizon.prefect.io) account (free personal tier)

### Getting a CTA API key

1. Sign up for and activate a [Bus Tracker account](http://www.ctabustracker.com/bustime/login.jsp)
2. Sign in, then go to **My Account** (upper right)
3. Follow the **Developer API** link to apply for a key

Full details: [CTA Developer Center — Bus Tracker APIs](https://www.transitchicago.com/developers/bustracker/)

## Deploying to Horizon

1. Push this repo to GitHub (public or private both work).
2. Sign in to [horizon.prefect.io](https://horizon.prefect.io) with GitHub and connect the repo.
3. On the configure screen, set:
   - **Entrypoint**: `main.py:mcp`
   - **Authentication**: enable this if you want to restrict access to your org rather than leaving the endpoint open — see [Rate limits](#rate-limits) below for why that matters here.
   - Dependencies are auto-detected from `pyproject.toml` — nothing to configure manually.
4. Set `CTA_URL` and `CTA_API_KEY` as environment variables/secrets for the deployment. Horizon's configuration screen should have a place for this; check the current UI when you get there, since the exact field wasn't fully documented at the time this was written.
5. Click **Deploy**. You'll get a live URL, typically in under 60 seconds:

   ```
   https://<your-server-name>.fastmcp.app/mcp
   ```

Horizon rebuilds and redeploys automatically on every push to `main`, and builds preview deployments for PRs.

### Verifying before you deploy

```bash
uv run fastmcp inspect main.py:mcp
```

This shows what Horizon will see when it runs the server — tools, resources, and prompts — so you can catch config problems locally before pushing.

### Connecting a client once deployed

Once live, MCP clients connect by URL rather than by spawning a local process:

```json
{
  "mcpServers": {
    "BusTime": {
      "url": "https://<your-server-name>.fastmcp.app/mcp"
    }
  }
}
```

Horizon also provides its own **Inspector** and **ChatMCP** for testing the deployed server directly in the browser before wiring up any client.

## Available tools

| Tool | Endpoint | Cached? |
|---|---|---|
| `get_routes()` | `/getroutes` | 24h |
| `get_directions(rt)` | `/getdirections` | 24h |
| `get_stops_for_route(rt, direction)` | `/getstops` | 30 min |
| `get_patterns(pid=, rt=)` | `/getpatterns` | 1h |
| `get_predictions_for_stop(stpid, rt, top)` | `/getpredictions` | never (real-time) |
| `get_vehicle(vid)` | `/getvehicles` | never (real-time) |
| `get_time(unix_time=)` | `/gettime` | never (real-time) |
| `get_locale_list(locale=, in_locale_language=)` | `/getlocalelist` | 24h |
| `get_detours(rt=, direction=)` | `/getdetours` | 2 min |
| `get_enhanced_detours()` | `/getenhanceddetours` | 2 min |

Typical call sequence: `get_routes` → `get_directions(rt)` → `get_stops_for_route(rt, direction)` → `get_predictions_for_stop(stpid, rt, top)`. See the server's `instructions` string in `main.py` for the full call graph, including how patterns, vehicles, and detours fit in.

**Known limitation:** `get_enhanced_detours` may return an `"Unsupported function"` error depending on your API key — this endpoint is GTFS-RT-oriented and appears to require CTA to enable it per-account. This is a CTA-side limitation, not a bug in this server.

## Caching

Cacheable endpoints use a simple in-memory TTL cache (see `_cached_get` in `main.py`) keyed by endpoint path + parameters. TTLs are set per-endpoint based on how often the underlying data actually changes.

The cache is **in-memory and per-instance**. It resets on restart/redeploy, and if Horizon ever runs multiple replicas of this server, each replica has its own independent cache rather than a shared one — still correct, just less effective at cutting duplicate calls than a single-instance setup.

## Testing

```bash
uv sync
uv run pytest -v
```

Tests use `pytest-asyncio` (auto mode) and `respx` to mock HTTP calls — no real network access or live API key is needed to run them. Run this before every push, since Horizon redeploys automatically on `main`.

## Project structure

```
.
├── main.py            # server entrypoint + all tool definitions
├── pyproject.toml      # dependencies (uv-managed; also read by Horizon for auto dependency detection)
├── tests/
│   ├── conftest.py     # makes main.py importable from tests/
│   └── test_main.py
└── README.md
```

## Rate limits

CTA's Bus Tracker API defaults to a 100,000 request/day limit per key. This server's caching helps reduce call volume for slow-changing data, but there's currently no built-in tracking or backoff as you approach that limit. This matters more once deployed publicly: a hosted URL can be called by anyone who finds it, unlike a local stdio server only you can spawn — enabling Horizon's authentication option (above) is the simplest way to keep usage scoped to people you trust.

TDQS

A3.8/5.0

Scored across 11 tools

Disambiguation2/5

Most tools target distinct aspects of CTA bus data, but the inclusion of 'add' (add two numbers) is completely unrelated to bus time, creating confusion about the server's purpose and when to use it.

Naming Consistency3/5

All bus-related tools follow a consistent 'get_<noun>' pattern, but the 'add' tool breaks this pattern entirely, resulting in moderate inconsistency.

Tool Count4/5

With 11 tools, the count is reasonable for a bus transit API. The 'add' tool is unnecessary but does not make the set too large or too small.

Completeness4/5

The bus tools cover core functionality: routes, directions, stops, predictions, vehicles, time, patterns, detours, and locale. Minor gaps exist (e.g., no route search), but the surface is largely complete for the domain.

Maintenance

ActivitySlowing
ResponsivenessNo issues