Skip to main content
ClaudeWave

MCP server for live Charlotte Area Transit System (CATS) bus and light rail data, built on the agency's public GTFS-Realtime feeds

MCP ServersOfficial Registry0 stars0 forksPythonMITUpdated today
ClaudeWave Trust Score
87/100
Trusted
Passed
  • Open-source license (MIT)
  • Actively maintained (<30d)
  • Clear description
  • Documented (README)
Last scanned: 9/11/2026
Install in Claude Code / Claude Desktop
Method: pip / Python · .
Claude Code CLI
claude mcp add cats-mcp -- python -m .
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "cats-mcp": {
      "command": "python",
      "args": ["-m", "cats_mcp"],
      "env": {
        "CATS_GOOGLE_CLIENT_SECRET": "<cats_google_client_secret>",
        "CATS_PUBLIC_URL": "<cats_public_url>"
      }
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Install first: pip install .
Detected environment variables
CATS_GOOGLE_CLIENT_SECRETCATS_PUBLIC_URL
Use cases

MCP Servers overview

<!-- mcp-name: io.github.ajwann/cats-mcp -->

# cats-mcp

[![CI](https://github.com/ajwann/cats-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ajwann/cats-mcp/actions/workflows/ci.yml)

An MCP server for live **Charlotte Area Transit System (CATS)** bus and light rail data,
built on the agency's public GTFS-Realtime feeds. It runs over **stdio**, launched by
the MCP client that uses it, or over **HTTP** with Google OAuth in front of it, for a
hosted server. Both transports serve the same three tools.

## Tools

| Tool | Purpose |
| --- | --- |
| `find_vehicle` | Locate one bus/train by vehicle number, or every vehicle on a route, and return GPS coordinates. |
| `list_vehicles` | Current GPS coordinates of every bus and train in service. |
| `get_arrivals` | Estimated arrival times at a specific stop or station. |

### `find_vehicle`

| Argument | Type | Notes |
| --- | --- | --- |
| `vehicle` | string | Vehicle number as shown on the bus/train, e.g. `2301`, `LRV307`. |
| `route` | string | Route to locate: `9`, `501`, `Blue Line`, `Mt. Holly Road`. |
| `mode` | `bus` \| `train` | Optional filter. |

At least one of `vehicle` or `route` is required. Returns position, heading, speed,
occupancy, headsign, and the next scheduled stop.

### `list_vehicles`

| Argument | Type | Notes |
| --- | --- | --- |
| `mode` | `bus` \| `train` | Optional filter. |
| `route` | string | Optional single-route filter. |
| `limit` | integer | Max vehicles to return (default and cap: 250). |

Includes `countsByMode` and `totalInService` so the total is visible even when the
list is truncated.

### `get_arrivals`

| Argument | Type | Notes |
| --- | --- | --- |
| `stop` | string | **Required.** Stop id (`02400`), stop code, or part of a stop name (`CTC Station`). |
| `route` | string | Optional route filter. |
| `mode` | `bus` \| `train` | Optional filter. |
| `limit` | integer | Max arrivals (default 10, cap 50). |

Returns minutes away, predicted and scheduled times, schedule deviation, the vehicle
number, and that vehicle's live position. When a name query is ambiguous, the best
match is used and the runners-up are listed under `otherStopsMatchingQuery`. Service
alerts affecting the stop or its routes are attached when present.

## Install

Requires Python 3.11+.

```bash
python3 -m venv .venv
.venv/bin/pip install .
```

## Transports

Pick one with `--transport` or `CATS_TRANSPORT`; the default is `stdio`.

```bash
cats-mcp                                  # stdio (default)
cats-mcp --transport http --port 8000     # streamable HTTP + Google OAuth
```

### stdio

For a server the client launches itself. No authentication: the client already owns
the process.

Register it with Claude Code:

```bash
claude mcp add cats -- /absolute/path/to/cats-mcp/.venv/bin/cats-mcp
```

Or in an MCP client config file:

```json
{
  "mcpServers": {
    "cats": {
      "command": "/absolute/path/to/cats-mcp/.venv/bin/cats-mcp"
    }
  }
}
```

`python -m cats_mcp` runs the same server, so any interpreter with the package
installed works as the command.

stdout carries MCP protocol traffic only; all diagnostics go to stderr.

### HTTP with Google OAuth

For a hosted server anyone with the URL can reach. Every request to `/mcp` needs a
bearer token, and the only way to get one is to sign in with a Google account that is
on the allow list.

**How the sign-in works.** MCP clients register themselves dynamically and expect an
authorization server at the MCP server's own origin. Google offers neither dynamic
registration nor tokens audience-restricted to a third-party resource, so this server
is its own OAuth 2.1 authorization server and delegates only the login to Google:

```
MCP client  <--OAuth-->  cats-mcp  <--OAuth-->  Google
```

Google's answer is used exactly once, to learn which account signed in. That email is
checked against the allow list, and only then does this server mint its own tokens.
Google's tokens are never handed to the client.

**One-time setup in Google Cloud.** At
[console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials),
create an **OAuth client ID** of type **Web application** and add one authorized
redirect URI:

```
https://your-public-url/auth/google/callback
```

It must match `CATS_PUBLIC_URL` exactly. The server logs the URI it expects at startup.
Copy the client ID and secret into the environment below.

**Run it.** `.env.example` lists every setting; the shell form is:

```bash
export CATS_GOOGLE_CLIENT_ID=...apps.googleusercontent.com
export CATS_GOOGLE_CLIENT_SECRET=...
export CATS_ALLOWED_EMAILS=you@example.com
export CATS_PUBLIC_URL=https://cats.example.com

cats-mcp --transport http --port 8000
```

Then point a client at `https://cats.example.com/mcp`; it discovers the rest and opens
a browser for the Google sign-in. In Claude Code:

```bash
claude mcp add --transport http cats https://cats.example.com/mcp
```

**Access is denied by default.** Startup fails unless `CATS_ALLOWED_EMAILS`,
`CATS_ALLOWED_DOMAINS`, or an explicit `CATS_ALLOW_ANY_GOOGLE_ACCOUNT=true` says who
may get in, so a misconfigured deployment is unreachable rather than open to every
Google account on the internet. Unverified Google addresses are always refused.

**Endpoints.**

| Path | Purpose |
| --- | --- |
| `/mcp` | The MCP endpoint. Requires `Authorization: Bearer <token>`. |
| `/.well-known/oauth-protected-resource/mcp` | Points clients at the authorization server. |
| `/.well-known/oauth-authorization-server` | This server's OAuth metadata. |
| `/register` | Dynamic client registration (RFC 7591). |
| `/authorize`, `/token`, `/revoke` | The OAuth endpoints. |
| `/auth/google/callback` | Where Google returns the user. |

[`scripts/install.sh`](scripts/install.sh) does a whole deployment: a system
user under `/opt`, a Cloudflare tunnel and its DNS record created over the API,
both systemd units, and a verification pass. No port forwarding, so it works
behind CGNAT or a locked router. See [`deploy/`](deploy/README.md).

**Deployment notes.**

- By default the server speaks plain HTTP and expects a tunnel or proxy to
  terminate TLS, which is what the install script sets up. Setting
  `CATS_TLS_CERT` and `CATS_TLS_KEY` instead makes it serve HTTPS itself, for a
  deployment with nothing in front of it.
- `CATS_PUBLIC_URL` is what clients dial and is this server's OAuth issuer
  identifier, so it must be the external URL, not the bind address.
- Token state is in memory and therefore per-process: restarting invalidates
  outstanding tokens, and running several replicas behind one hostname would need a
  shared store instead.
- Access tokens last an hour and refresh tokens 30 days, both rotated on refresh.

## Data sources

Realtime (GTFS-Realtime protobuf, refreshed every 20s):

- `https://gtfsrealtime.ridetransit.org/GTFSRealTime/Vehicle/VehiclePositions.pb`
- `https://gtfsrealtime.ridetransit.org/GTFSRealTime/TripUpdate/TripUpdates.pb`
- `https://gtfsrealtime.ridetransit.org/GTFSRealTime/Alert/Alerts.pb`

Static schedule (cached 6h), used to turn feed identifiers into route names, stop
names, and coordinates:

- `https://gtfsrealtime.ridetransit.org/GTFSStatic/api/GTFSDownload/GTFS.zip`

Only `routes.txt`, `stops.txt`, and `trips.txt` are read; `stop_times.txt` and
`shapes.txt` are the bulk of the archive and are not needed.

## Feed quirks this server works around

Verified against live feed captures:

- **`VehiclePosition.stop_id` and `current_stop_sequence` are unusable.** None of the
  158 vehicle stop ids in a sample capture matched any stop in the published schedule,
  and reported sequence numbers exceeded the trip's own stop count (e.g. sequence 192
  on a 52-stop trip). This server never surfaces them; next-stop data comes from the
  TripUpdates feed instead, whose stop ids resolve 100%.
- **`StopTimeEvent.delay` is never populated.** Schedule deviation is computed from
  `time` minus `scheduled_time`, which are both present.
- **TripUpdates cover ~83% of active vehicles**, so `nextStop` is omitted rather than
  guessed for the remainder.
- **Route matching is exact-first**, so a query of `5` returns route 5, not 501 or 510.

## Behavior notes

- Arrival predictions already in the past are filtered out; no negative ETAs.
- Feed responses are capped in size and time-bounded; one slow feed cannot hang a call.
- Concurrent calls share a single in-flight fetch per feed, and one call giving up does
  not abort a fetch the others are awaiting.
- If a refresh fails but cached data exists, the last good data is served rather than
  an error. `feedAgeSeconds` on every response shows how stale it is.
- The alerts feed is supplementary: if it fails, `get_arrivals` still returns arrivals.
- Times are ISO 8601 UTC; coordinates are WGS84 decimal degrees.

## Configuration

### Feeds (both transports)

All optional; defaults target the CATS feeds above. Durations are in milliseconds.

| Variable | Default |
| --- | --- |
| `CATS_VEHICLE_POSITIONS_URL` | CATS vehicle positions feed |
| `CATS_TRIP_UPDATES_URL` | CATS trip updates feed |
| `CATS_ALERTS_URL` | CATS alerts feed |
| `CATS_STATIC_GTFS_URL` | CATS static GTFS zip |
| `CATS_REALTIME_TTL_MS` | `20000` |
| `CATS_STATIC_TTL_MS` | `21600000` |
| `CATS_REQUEST_TIMEOUT_MS` | `30000` |
| `CATS_MAX_FEED_BYTES` | `33554432` |
| `CATS_MAX_STATIC_BYTES` | `268435456` |

Feed URLs must be `http` or `https`; anything else is rejected at startup.

### Transport

| Variable | CLI | Default |
| --- | --- | --- |
| `CATS_TRANSPORT` | `--transport` | `stdio` |

### HTTP transport

Read only when `--transport http` is selected.

| Variable | CLI | Default | Notes |
| --- | --- | --- | --- |
| `CATS_HTTP_HOST` | `--host` | `127.0.0.1` | Bind address. |
| `CATS_HTTP_PORT` | `--port` | `8000` | Bind port. |
| `CATS_PUBLIC_URL` | `--public-url` | `http://localhost:<port>` | External origin; the OAuth issuer. |
| `CATS_GOOGLE_CLIENT_ID` | | **required** | From

What people ask about cats-mcp

What is ajwann/cats-mcp?

+

ajwann/cats-mcp is mcp servers for the Claude AI ecosystem. MCP server for live Charlotte Area Transit System (CATS) bus and light rail data, built on the agency's public GTFS-Realtime feeds It has 0 GitHub stars and its last recorded update is dated 2026-09-11.

How do I install cats-mcp?

+

You can install cats-mcp by cloning the repository (https://github.com/ajwann/cats-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is ajwann/cats-mcp safe to use?

+

Our security agent has analyzed ajwann/cats-mcp and assigned a Trust Score of 87/100 (tier: Trusted). See the full breakdown of passed checks and flags on this page.

Who maintains ajwann/cats-mcp?

+

ajwann/cats-mcp is maintained by ajwann. The last recorded GitHub activity is dated 2026-09-11, with 0 open issues.

Are there alternatives to cats-mcp?

+

Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.

Deploy cats-mcp to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: ajwann/cats-mcp
[![Featured on ClaudeWave](https://claudewave.com/api/badge/ajwann-cats-mcp)](https://claudewave.com/repo/ajwann-cats-mcp)
<a href="https://claudewave.com/repo/ajwann-cats-mcp"><img src="https://claudewave.com/api/badge/ajwann-cats-mcp" alt="Featured on ClaudeWave: ajwann/cats-mcp" width="320" height="64" /></a>

More MCP Servers

cats-mcp alternatives