cats-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cats-mcpWhen is the next bus arriving at CTC Station?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
cats-mcp
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.
Tools
Tool | Purpose |
| Locate one bus/train by vehicle number, or every vehicle on a route, and return GPS coordinates. |
| Current GPS coordinates of every bus and train in service. |
| Estimated arrival times at a specific stop or station. |
find_vehicle
Argument | Type | Notes |
| string | Vehicle number as shown on the bus/train, e.g. |
| string | Route to locate: |
|
| 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 |
|
| Optional filter. |
| string | Optional single-route filter. |
| 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 |
| string | Required. Stop id ( |
| string | Optional route filter. |
|
| Optional filter. |
| 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.
Related MCP server: OC Transpo MCP Server
Install
Requires Python 3.11+.
python3 -m venv .venv
.venv/bin/pip install .Run it
Register it with Claude Code:
claude mcp add cats -- /absolute/path/to/cats-mcp/.venv/bin/cats-mcpOr in an MCP client config file:
{
"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.
Data sources
Realtime (GTFS-Realtime protobuf, refreshed every 20s):
https://gtfsrealtime.ridetransit.org/GTFSRealTime/Vehicle/VehiclePositions.pbhttps://gtfsrealtime.ridetransit.org/GTFSRealTime/TripUpdate/TripUpdates.pbhttps://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_idandcurrent_stop_sequenceare 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.delayis never populated. Schedule deviation is computed fromtimeminusscheduled_time, which are both present.TripUpdates cover ~83% of active vehicles, so
nextStopis omitted rather than guessed for the remainder.Route matching is exact-first, so a query of
5returns 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.
feedAgeSecondson every response shows how stale it is.The alerts feed is supplementary: if it fails,
get_arrivalsstill returns arrivals.Times are ISO 8601 UTC; coordinates are WGS84 decimal degrees.
Configuration
All optional; defaults target the CATS feeds above. Durations are in milliseconds.
Variable | Default |
| CATS vehicle positions feed |
| CATS trip updates feed |
| CATS alerts feed |
| CATS static GTFS zip |
|
|
|
|
|
|
|
|
|
|
Feed URLs must be http or https; anything else is rejected at startup.
Layout
Module | Role |
| Environment parsing and validation |
| Bounded, time-limited HTTP fetch |
| TTL cache with single-flight refresh |
| GTFS-flavored CSV reading |
| Static schedule: routes, stops, trips |
| GTFS-Realtime protobuf decoding |
| Domain layer: joins realtime to schedule, resolves queries |
| The three tools' behavior and JSON payloads |
| MCP tool registration and schemas |
| stdio entry point |
Development
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest # 80 tests, offline against recorded feed fixtures
.venv/bin/mypy # strict
.venv/bin/ruff check .
.venv/bin/ruff format .Tests run against protobuf and GTFS fixtures captured from the live feeds, so they are
deterministic and make no network calls. tests/test_feed_http.py is the exception: it
serves canned responses from a loopback socket so the byte cap and timeout are exercised
for real.
Available Tools
3 toolsfind_vehicleFind a bus or trainARead-only
Locate a specific CATS bus or train and return its current GPS coordinates. Give "vehicle" for a vehicle number (e.g. "2301"), or "route" to get every vehicle currently running a route (e.g. "9", "501", "Blue Line"). Includes heading, speed, occupancy, and next scheduled stop when available.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | ||
| route | No | ||
| vehicle | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true. The description adds useful behavioral context by specifying the return data (GPS coordinates, heading, speed, occupancy, next scheduled stop) and noting that the stop is included 'when available'. It does not contradict annotations, and the mention of 'when available' aligns with the open-world hint. However, it does not disclose behavior such as what happens when no match is found or whether both vehicle and route are allowed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, front-loaded with the primary purpose and immediately followed by usage examples. Every word contributes to understanding the tool's behavior and parameters. There is no redundant or filler content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the main usage patterns (vehicle- and route-based lookup) and mentions the output fields. However, it does not specify behavior when both vehicle and route are provided, when neither is provided, or how the mode parameter applies. Given the existence of an output schema, the missing parameter-precedence details are a gap that could lead to incorrect usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides descriptions for all three parameters (mode, route, vehicle). The description adds extra meaning by explaining the mutually exclusive use of vehicle vs. route ('Give vehicle for a vehicle number... or route to get every vehicle'). It does not clarify the mode parameter or interaction between fields, so the added value over the schema is limited.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with a specific verb ('Locate'), names the resource ('a specific CATS bus or train'), and states the output ('current GPS coordinates'). It also distinguishes two search modes (by vehicle number or by route) and clarifies the scope ('every vehicle currently running a route'). This clearly differentiates it from siblings like list_vehicles and get_arrivals, which likely list all or handle scheduled times.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives parameter usage instructions ('Give vehicle for a vehicle number... or route to get every vehicle') but provides no guidance on when to choose this tool over list_vehicles or get_arrivals. It does not mention exclusions, alternatives, or when the tool is not appropriate, leaving the agent to infer tool selection from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_arrivalsGet arrival times at a stopARead-only
Estimated arrival times of buses or trains at a specific stop or station. Accepts a stop id, stop code, or part of a stop name. Reports minutes away, schedule deviation, the vehicle number, and any service alerts for that stop.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Only show bus or train arrivals. | |
| stop | Yes | Stop id, stop code, or part of a stop name, e.g. "02400" or "CTC Station". | |
| limit | No | Maximum arrivals to return. | |
| route | No | Only show arrivals for this route. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint and openWorldHint; the description helps confirm these by saying 'Estimated'. It adds no operational limitations beyond what the schema provides, and it doesn't mention any rate limits, pagination, or fallback/ambiguous name handling. It does add 'service alerts' as a kind of output, which is lightly useful, so the description is adequate but not deep.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences capture the main purpose, input flexibility, and key fields. The most important information is placed first. No filler is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only tool with four parameters fully documented in the schema and an output schema present, the description provides enough context for an agent to know what the tool is for, what it accepts, and what it returns. It does not need to restate schemas because those are adjacent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 100% coverage of four parameters, each with its own description. The text like 'stop id, stop code, or part of a stop name' mainly restates the schema's stop description. It adds no extra meaning for limit, mode, or route, so meaning comes primarily from the schema itself.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the resource ('estimated arrival times of buses or trains') and the specific action ('at a specific stop or station'), making the tool's function explicit. It also lists the input formats and useful output fields, which differentiates it from sibling tools find_vehicle and list_vehicles.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It implies when to use the tool (when you need arrival times at a stop), but it never states an explicit 'when-not-to-use' or contrasts with siblings. No exclusions are given, so an agent can infer intent, but someone selecting among the three siblings must rely on whatever context the user provides without extra guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_vehiclesList all vehicle positionsARead-only
Return the current GPS coordinates of every CATS bus and train in service. Optionally filter to buses or trains, or to a single route.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | ||
| limit | No | Maximum vehicles to return. | |
| route | No | Restrict results to one route. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is established. The description adds the behavioral fact that it returns 'current' coordinates and only vehicles 'in service,' which clarifies the data scope. It does not go beyond that (e.g., mention of latency, rate limits, or pagination), so it makes a modest addition beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no waste. The core action is front-loaded ('Return the current GPS coordinates...'), and the optional filters are mentioned compactly. Everything written earns its place, making it highly efficient for an agent to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema is present, so return format details are not needed in the description. The tool is a simple listing operation with optional filters; the description covers the essence, and the schema covers parameters. The only minor gap is the lack of explicit mention of potential limits (like pagination), but the limit parameter and output schema mitigate this. Overall, it is sufficiently complete for an agent to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 67%, meaning most parameters already have meaning in the schema (mode, limit, and route each have descriptions). The description repeats these filters ('buses or trains', 'single route') without adding extra semantics like value formats or edge cases. Since the schema already covers the parameters, the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Return'), a clear resource ('GPS coordinates of every CATS bus and train in service'), and explicitly distinguishes this from a single-vehicle lookup ('every'). It also mentions optional filters, which makes the tool's scope unmistakable and separates it from the sibling find_vehicle.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description says 'Optionally filter to buses or trains, or to a single route,' which gives clear context on how to narrow results. However, it does not explicitly state when to prefer this tool over siblings like find_vehicle (e.g., 'for all vehicles, use this; for a specific one, use find_vehicle'). The 'every' phrasing implies bulk listing but does not name alternatives or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
3 tool updates
v1.0.0- First observed
find_vehicle - First observed
get_arrivals - First observed
list_vehicles
TDQS
Scored across 3 tools
The tools are mostly distinct, but find_vehicle and list_vehicles overlap when finding all vehicles on a route. find_vehicle emphasizes locating a specific vehicle or route vehicles with detailed info, while list_vehicles provides a fleet-wide listing with filters, so descriptions help but an agent could still be confused.
All tool names follow a clean verb_noun snake_case pattern: find_vehicle, list_vehicles, get_arrivals. While list_vehicles uses plural and find_vehicle singular, the convention is consistent and predictable.
Three tools is a tight, well-scoped set for a transit tracking server. Each tool covers a distinct core need: locating vehicles, listing vehicles, and retrieving arrivals. No bloat, and the count is within the ideal range.
The tool surface covers the essential real-time tracking workflows: vehicle location, fleet listing, and stop arrivals. Minor gaps exist such as no route or stop enumeration, but agents can work around these using the accepted stop codes and route filters.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
Read-only public transit departures, stop search, and city coverage for bus and train users.
Get real-time NYC bus arrivals, live vehicle locations, and service alerts. Plan trips between any…
MBTA MCP — Boston real-time transit via the MBTA v3 API (api-v3.mbta.com)
Related MCP Servers
- AlicenseAqualityCmaintenanceProvides real-time and scheduled bus data for the Centre Area Transportation Authority (CATA) in State College, PA. Enables users to track live bus positions, get arrival predictions, search stops, view routes, and receive service alerts through natural language queries.7MIT
- FlicenseNot gradedqualityDmaintenanceProvides real-time transit data for OC Transpo in Ottawa, including live vehicle positions and trip updates via GTFS-RT feeds. It enables AI agents to monitor arrival delays, schedule changes, and transit telemetry through the Model Context Protocol.2-
- AlicenseNot gradedqualityCmaintenanceEnables real-time access to Boston MBTA transit data (subway, bus, commuter rail, etc.) via the MBTA v3 API.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables querying global transit data including agencies, routes, stops, and departures through a GTFS aggregator.6MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/ajwann/CATS-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server