Skip to main content
Glama

Live answers about Toronto's transit network, for Claude and other MCP clients.

ttc-mcp is a Model Context Protocol server for the Toronto Transit Commission (TTC). It lets an assistant answer the questions riders actually ask: when the next streetcar comes, whether a subway line is closed and where the shuttle buses run, how to get across town by a given time and around a closure, whether a station is step-free today. It reads the TTC's timetables and live feeds when you ask, says how fresh each answer is, and attaches service disruptions to every answer they touch.

It is an independent, single-user project, not affiliated with or endorsed by the TTC or the City of Toronto.

Contents

Related MCP server: ttc-mcp

What you can ask

You ask in plain language and the client picks the tools. Some examples:

Ask

Tools

What comes back

How do I get from Spadina to Pape?

connections

Timetable-exact trips: real departures and arrivals on every leg, up to three transfers, a departure window or an arrival time, the next departures of the first ride, and every disruption on the way with the legs it affects

I'm at the Distillery District. How do I get to Union?

a maps tool to geocode, then connections

The same, starting from the place's coordinates

I have to be at Union by 9. When do I leave Kipling?

connections with arrive_by

The latest trips that still arrive in time, each with its exact times

When's the next 504 at King and Spadina?

find_stops, arrivals

Predicted minutes with crowding, or timetable minutes and a note when nothing is predicted

When's the next train at Bloor-Yonge?

next_trains

Minutes for every platform, by line and direction

Is anything wrong on the subway?

service_status

Closures with their hours and shuttles, reduced speed zones, suspended or detoured surface routes, elevators and escalators out of service

Is Queen station step-free right now?

accessibility

Platform and entrance flags, each elevator's live status, and a yes, no or unknown verdict with its reason

Why is my bus skipping my stop?

detours, route_stops

The skipped stops, where the bus leaves and rejoins its route, the detour's path and the TTC's reason when it gives one

Where are the 505 streetcars?

vehicles

Positions, bearing, speed, current stop and crowding

Does the 300 Blue Night run tonight, and when is the last bus?

routes, service_hours

Whether it runs that day, and its first and last trips

What leaves stop 15461 after 6 pm?

scheduled_departures

Timetable departures, including trips that run past midnight

Show me the 7 Bathurst on a map.

route_shape

The route's line as an encoded polyline or GeoJSON, with its stops

What's changing on the 501 this board period?

service_changes

The TTC's route changes for the period, with their dates

Is the transit data up to date?

feed_status

Each source's age, cache state and failures, and the timetable's dates

The server doesn't turn addresses into coordinates. Pair it with a maps MCP server, such as Google Maps, and the client geocodes first; connections and nearest_stops take {lat, lon}. The server's instructions tell the client to start trip questions with connections and "what's going on" questions with service_status.

An answer during a closure

On Monday 12 October 2026, Line 2 had no service between St George and Broadview from 06:00 to 15:00 for structural maintenance, with shuttle buses running. Asked for the next trains at St George at 13:24, the server answered (abridged: keys removed, values unchanged):

{
  "station": {"id": "43269", "name": "St George"},
  "platforms": [
    {"platform": "13855", "line": "2", "direction_text": "Westbound to Kipling",
     "next_min": [0], "basis": "realtime", "service": "normal"},
    {"platform": "13856", "line": "2", "direction_text": "Eastbound to Kennedy",
     "next_min": [], "ntas_min": [2, 6, 13], "service": "suspended",
     "note": "NTAS keeps publishing predictions inside a closure; this platform is suspended"},
    {"platform": "13857", "line": "1", "direction_text": "Southbound to Finch via Union",
     "next_min": [2, 5, 10], "basis": "realtime", "service": "normal"},
    {"platform": "13858", "line": "1", "direction_text": "Northbound to Vaughan Metropolitan Centre",
     "next_min": [6, 8, 10], "basis": "realtime", "service": "normal"}
  ],
  "disruptions": [
    {"type": "closure", "alert_id": "77820", "span": ["St George", "Broadview"], "active_now": true,
     "period": ["2026-10-12T06:00-04:00", "2026-10-12T15:00-04:00"], "shuttle": "Running",
     "role": "shuttle_terminus",
     "text": "Line 2 Bloor-Danforth: No service between St George and Broadview stations due to structural maintenance. Shuttle buses are running."}
  ],
  "meta": {"source": "ntas", "fetched_at": "2026-10-12T13:24:52-04:00", "cache": "live",
           "staleness": "fresh", "static_feed": "S1000542",
           "advisories": {"cache": "live", "staleness": "fresh", "errors": null}}
}

Three things to notice:

  • The TTC's train-prediction service (NTAS) was still predicting eastbound trains in 2, 6 and 13 minutes. The closure decides instead: the platform is suspended, next_min is empty, and NTAS's numbers stay visible as ntas_min.

  • St George is the end of the closure, so the westbound platform, facing the open part of the line, kept real predictions. The closure is tagged shuttle_terminus: this is where the shuttle buses start.

  • meta says the predictions were fetched live and are fresh, and that the disruption feeds answered.

connections plans across a closure on the shuttle buses. The unit suite's fixtures carry a real nightly Line 1 closure between York Mills and Eglinton from 23:00, with shuttle buses (alert 77519, captured 2026-10-05). Asked at 23:30 for a trip from Finch to Union, the server answers (abridged the same way):

{
  "from": {"station": "43277", "name": "Finch"}, "to": {"station": "15772", "name": "Union"},
  "depart_at": "2026-10-06T23:30-04:00", "window_min": 30,
  "options": [
    {"rank": 1, "transfers": 2, "depart_at": "2026-10-06T23:33-04:00", "arrive_at": "2026-10-07T00:27-04:00",
     "duration_min": 54, "basis": "schedule", "estimate": true,
     "legs": [
       {"mode": "ride", "route": "1", "trip_id": "134929439",
        "board": {"number": "14111", "name": "Finch Station - Southbound Platform"},
        "alight": {"number": "13792", "name": "York Mills Station - Southbound Platform"},
        "depart": "2026-10-06T23:33-04:00", "arrive": "2026-10-06T23:40-04:00", "min": 7, "basis": "schedule"},
       {"mode": "transfer", "from": {"number": "13792"}, "to": {"number": "13791", "name": "York Mills Station - Northbound Platform"},
        "distance_m": 134, "via": "pathways", "depart": "2026-10-06T23:40-04:00", "arrive": "2026-10-06T23:48-04:00", "min": 8},
       {"mode": "shuttle", "line": "1", "from": {"station": "43276", "name": "York Mills"}, "to": {"station": "43274", "name": "Eglinton"},
        "depart": "2026-10-06T23:48-04:00", "arrive": "2026-10-07T00:08-04:00", "min": 20, "basis": "estimate", "alert_id": "77519",
        "note": "assumed 5-minute headway and 15 km/h over 4.4 km of track with a 1-minute stop per station; the TTC publishes no shuttle timetable (alert 77519)"},
       {"mode": "transfer", "from": {"number": "13795"}, "to": {"number": "13795", "name": "Eglinton Station - Southbound Platform"},
        "distance_m": 0, "via": "walk", "depart": "2026-10-07T00:08-04:00", "arrive": "2026-10-07T00:11-04:00", "min": 3},
       {"mode": "ride", "route": "1", "trip_id": "134929444",
        "board": {"number": "13795"}, "alight": {"number": "13815", "name": "Union Station - Northbound Platform Towards Vaughan Metropolitan Centre"},
        "depart": "2026-10-07T00:11-04:00", "arrive": "2026-10-07T00:27-04:00", "min": 16, "basis": "schedule"}
     ],
     "disruptions": [
       {"type": "rsz", "alert_id": "77535", "span": ["Sheppard-Yonge", "York Mills"], "affects": [1]},
       {"type": "closure", "alert_id": "77519", "span": ["York Mills", "Eglinton"], "active_now": true, "shuttle": "Will Operate", "affects": [3]}
     ]}
  ]
}
  • The two train rides are real trips with their timetable times. The shuttle leg's 20 minutes are an estimate: its note says what it assumes, basis: estimate marks the leg, and estimate: true the trip, which would rank after any trip that needs no estimate.

  • The shuttle's stop is placed at a platform of each station, so the walk from the southbound to the northbound platform at York Mills stands for reaching the shuttle buses. The 8 minutes between its clocks cover the 104-second walk, the two-minute change buffer and a full five-minute wait for a bus.

  • The closure names the leg it affects, 3, the shuttle; the reduced speed zone between Sheppard-Yonge and York Mills names the first train ride.

The tools

Nineteen tools in four groups. Station inputs take an id, a name or a common alias ("Dundas" finds TMU, "Bloor" finds Bloor-Yonge); stop inputs take the public stop number posted at the stop. The output shapes are specified in docs/spec.md section 4.4.

Timetable and network, answered from the TTC's published GTFS on disk:

Tool

Takes

Answers with

find_stops

a name, stop number or station alias; optional route, direction, mode

Matching stops, platforms and stations, with the routes and headsigns each serves

nearest_stops

lat, lon; optional radius (500 m), limit, kind

Nearby stops and stations with walking minutes, routes served and the disruptions touching each (active_now says which apply now)

stop

a stop number

One stop or platform: location, wheelchair flag, routes, station and disruptions

station

a station id, name or alias

Rail platforms with open or closed status, bus bays and streetcar platforms, entrances, the accessibility summary and service per line

routes

optional series, mode, overnight

Every route with its mode, series (Blue Night, express and community among them) and service hours

route_stops

route, direction (0 or 1); optional pattern

Ordered stops per stop pattern, with typical minutes and each stop's status: open, closed or skipped by a detour

service_hours

route; optional date

Whether the route runs that day, and its first and last trips

scheduled_departures

stop; optional route, time, limit

Timetable departures from a time, including trips past midnight

Live, fetched from the TTC when asked:

Tool

Takes

Answers with

arrivals

a stop number or rail platform; optional route, limit

Next vehicles with minutes, crowding and detour flags, or timetable minutes with a note

next_trains

a station or platform

Next trains on every platform, or why a platform has none

vehicles

a route or fleet number

Bus and streetcar positions, bearing, speed, current stop and crowding

feed_status

optional probe

The age and health of every source and of the timetable; probe: true checks each source now

Disruptions:

Tool

Takes

Answers with

alerts

optional route, station, stop, type

Current alerts in one shape: closures, suspensions, detours, reduced speed zones, delays, elevator and escalator outages

service_status

nothing

The network picture, and the first call for "what's going on"

detours

optional route

Active detours with skipped stops, the detour's path, trips affected and the reason

service_changes

optional route, date

Route changes for the current board period

Derived:

Tool

Takes

Answers with

route_shape

route, direction; optional pattern, format, simplification, a stop-to-stop span

The route's geometry with its stops

accessibility

a station or stop

Platform and entrance flags, elevators and escalators with live status, and a step-free verdict with its basis

connections

origin and destination (a stop, a station or {lat, lon}); optional maximum transfers (0 to 3), walking distance, a departure time (depart_at) with a window (window_min, 30 minutes by default) or an arrival time (arrive_by), limit

Timetable-exact trips: real departures and arrivals on every leg, up to three transfers, a departure window or an arrival time, the next departures of the first ride, and every disruption on the way with the legs it affects

Every answer carries meta:

  • source: the feeds that answered.

  • fetched_at, and feed_timestamp when the feed publishes one.

  • cache: live (just fetched), ttl (reused within the feed's refresh interval), stale_fallback (the source failed and this is its last good answer), static (timetable only), or null when no live source answered at all.

  • staleness: fresh or stale for live data, judged against the feed's own clock; fresh, expiring or expired for the timetable; unavailable when there is nothing to judge.

  • static_feed: the timetable version in use.

  • advisories: the same for the disruption feeds when the answer includes disruptions, with errors naming any feed that failed. The four disruption tools (alerts, service_status, detours, service_changes) describe those feeds in meta itself and name failed ones in meta.errors.

Rows that give times also carry basis: realtime or schedule, and estimate on a connections shuttle leg. In connections a ride is realtime when a live trip update moved its trip, and the trip as a whole carries its first ride's basis. meta describes the fetch, so a source that answered with no predictions reads live and fresh while its rows fall back to basis: schedule.

Design

The specification records each decision with its evidence; section 7 is the summary. The ones that shape what you see:

  • It fetches only when asked. There is no background polling. A question triggers the requests it needs, each source is cached for its own refresh interval (from 10 seconds for train predictions to 10 minutes for service changes), and simultaneous questions share one request. An idle server sends nothing, which is light on the TTC's servers and lets a hosted copy scale to zero.

  • It says how fresh every answer is. A stale cached answer is never labelled fresh, and timetable minutes always carry basis: schedule (see meta above).

  • Disruptions travel with the answer. Closures, detours, reduced speed zones, delays and elevator outages are attached to the stops, stations, arrivals and trips they affect, and the trip planner never boards, drops off or transfers you at a station closed at the time you ask about, never stops a detoured bus at a stop its detour skips, and never puts you on a train across a section closed at that time.

  • Trips are planned on the timetable itself. connections searches the trips the TTC actually runs (the RAPTOR algorithm, over an index built with the timetable and loaded into memory once per timetable), so every ride is a real trip with its own departure and arrival. A change between vehicles costs the walk, timed through the station's mapped passageways where the TTC publishes them, plus a two-minute buffer. The answer is the fastest trip for each number of transfers, from one moment, across a departure window, or by an arrival time. For a question within an hour of now it reads the TTC's live trip updates for buses, streetcars and the subway, so the trips it boards run at their predicted times; Lines 5 and 6 publish no trip updates and stay on the timetable.

  • Closures follow how the TTC runs them. Stations inside a closure have no trains. Each end station keeps running on the platform facing the open part of the line, and that is where the shuttle buses start. A train prediction for a closed platform is set aside, not trusted.

  • It says which numbers are estimates: a shuttle leg's minutes assume a headway and a bus speed, the note states them, and the trip is flagged estimate. Every ride is a real trip, at its timetable times or, near now, at the times the TTC's live trip updates predict. Crowding reads "No info" without data. A step-free verdict is unknown when a station's elevators aren't fully mapped. Timetable fallbacks say basis: schedule.

  • Timetables come from the TTC's published GTFS, built into a local SQLite file, with the trip planner's index, and rebuilt only when the City of Toronto's portal publishes a new one. During a board-period changeover both timetables stay loaded, so trips from either resolve.

  • Live data comes from the TTC's own feeds: BusTime for buses and streetcars, NTAS for the subway and LRT, and the TTC's alert and GTFS-Realtime feeds (alerts, detours, and trip updates for buses, streetcars and the subway). Every request names the project in its User-Agent, and a failing source is backed off (see below).

  • Lines 5 and 6 are rapid transit everywhere: stations, platforms, closures and train predictions.

  • Accessibility reads the rail platforms, not the station's own flag (which the feed sets on every station) and not its bus bays, and takes elevator status from live alerts.

  • No geocoding. Addresses are a maps tool's job, so the server needs no maps API key.

  • One person, gated when hosted. A hosted copy sits behind Google sign-in with an email allowlist, and it speaks stateless HTTP so an idle connection never keeps an instance running.

  • Built on FastMCP 4 and Python 3.14.

When service is degraded

Two kinds of trouble: a data source failing, and the transit network itself being disrupted. Either way the server answers what it can and says what it couldn't.

A data source fails

Source

Fetched at most every

Last good answer kept for

Called stale after

Bus and streetcar predictions (BusTime)

30 s

10 min

90 s

Vehicle positions and trip updates (BusTime GTFS-Realtime)

30 s

10 min

90 s

Subway trip updates (GTFS-Realtime)

30 s

10 min

60 s

Train predictions (NTAS)

10 s

5 min

60 s

alerts.ttc.ca

25 s

1 h

10 min

GTFS-Realtime alerts

60 s

1 h

10 min

Detours

5 min

1 h

30 min

ttc.ca service changes

10 min

1 h

30 min

  • A request fails or times out (after 10 seconds, or 5 for the alert, detour and service-change feeds): the source's last good answer is used, marked cache: stale_fallback and staleness: stale, for as long as the table allows.

  • Past that window, live answers fall back to the timetable: arrivals and next_trains give scheduled minutes with basis: schedule and a note naming the failure, and vehicles returns no vehicles with a note. A disruption feed that fails is named in meta.advisories.errors, or in meta.errors for the four disruption tools; the answer still comes, but read "no disruptions" there as unknown, not as all clear.

  • A trip-update feed fails: connections plans that part of the network on the timetable, its rides read basis: schedule, and meta.source names only the feeds that answered. The trip-update feeds are fetched only for questions within an hour of now.

  • A source that keeps failing is put on hold. After each failure it isn't asked again for a pause that doubles with every consecutive failure, starting at twice its refresh interval and capped at a quarter of how long its last good answer is kept, so a struggling TTC server isn't hammered. Holds are per request: per stop for bus and streetcar predictions, per platform for train predictions, per feed otherwise. feed_status shows each source's failures, when it will next be tried (retry_after) and how many HTTP 403 or 429 refusals it has seen; feed_status with probe: true asks every source once, hold or not.

  • No prediction isn't no service. An empty answer from NTAS (common on Line 6, and normal late at night) reads service: no_prediction with timetable minutes. BusTime's own messages are passed on in the note the same way.

  • Rail lines publish no vehicle positions or crowding, so vehicles answers that the rail lines (1, 2, 4, 5 and 6) are unavailable and points to next_trains.

The network is disrupted

  • A subway or LRT closure. Platforms inside the closed section, and each end station's platform facing into it, are suspended: no minutes, with whatever NTAS still predicts set aside in ntas_min, as in the example above. The end stations keep real minutes on the platform facing the open line (service: normal in next_trains, while station reads that line as reduced), and the closure is tagged shuttle_terminus. A closure that ends at a terminal, or covers one station, closes both platforms there. Trips across the gap become ride, shuttle, ride with the shuttle estimated and flagged: the shuttle leg's minutes come from an assumed headway and bus speed that its note states, the trip carries estimate: true, and it ranks after every trip that needs no estimate. The shuttle stop is placed at one platform of each station, so a trip can show a walk between two platforms of an end station: that walk stands for reaching the shuttle buses. When the TTC runs no shuttle, connections routes around the closure on other lines or says why it found nothing.

  • Planned and nightly closures carry their windows, and active_now says whether one applies at this minute. connections with a future depart_at or an arrive_by plans against the closures in force at the time you give. A departure window or an arrive-by search can span an hour or more, so when a closure on a line one of the trips rides starts or ends inside it, the answer's note names the closure and the time: trips on the far side of that moment were still planned with the closure as it stood at the time you gave.

  • A surface detour. Skipped stops show as skipped_by_detour in route_stops and arrival rows carry the detour's id. In connections the detoured route's trips skip them with the published delay; other routes keep serving the stop. When a detour can't be matched to its route's trips, connections avoids its skipped stops altogether: it plans from the served stops nearby and says so.

  • Reduced speed zones, in their direction of travel, and delays are attached to the legs of a trip that they affect, and affects names those legs.

  • An elevator out of service shows in accessibility with its alert, and the step-free verdict turns to no, noting that another elevator may still give a step-free path. Outages announced for later are listed under upcoming and leave today's verdict alone.

The timetable is missing or old

  • No timetable yet: every tool except feed_status answers with an error asking you to run ttc-mcp-build; the live and disruption tools need the timetable too, to resolve stops, stations and routes.

  • A timetable built by an older version of the server is skipped (the server's log names it) and the tools answer with the same error; ttc-mcp-build rebuilds it, and so does a local stdio server's startup check. The trip planner's index, added in October 2026, is one such change.

  • An ageing timetable: within three days of its end date, with no newer one loaded, staleness reads expiring. Once no loaded timetable covers today, the tools answer with the same error, with staleness: expired, until a build loads the next one. The TTC publishes each board period's timetable shortly before it starts (in September 2026, two days ahead once and on the day itself once), and the daily check picks it up.

  • The City's portal is unreachable: a local server's startup check, its 24-hour recheck and the hosted daily Job log the failure and keep the timetable they have. A check gives up connecting after 10 seconds, so the server still starts promptly, and with no timetable on disk yet it starts anyway and its tools say the timetable isn't built yet.

  • Hosted, the bucket can't be read: a new instance exits at startup and Cloud Run reports the failed start; an empty bucket starts a server whose tools, feed_status aside, say the timetable isn't built yet. A failed pull on the six-hour timer keeps the timetable already loaded.

Running it

What you need

  • uv, which installs Python 3.14 for you.

  • About 450 MB of disk: each timetable is a 139 MB SQLite file, two of them during a board-period changeover, plus the 170 MB of GTFS zips the last build downloaded. Ended timetables stay on disk after a build drops them from manifest.json; delete those static-*.sqlite files by hand.

  • About 70 MB of memory for each loaded timetable's trip planner index, on top of the server itself.

  • Network access to the City of Toronto's open data portal, to build the timetable, and to the TTC's servers, for live answers.

Locally, with Claude Code or Claude Desktop

git clone https://github.com/LunaParker/ttc-mcp.git
cd ttc-mcp
uv sync
uv run ttc-mcp-build

The build downloads the TTC's GTFS from the City's portal and builds the timetable into ~/.cache/ttc-mcp, in about a minute on a fast connection. Then add the server to your client.

Claude Code, for every project (--scope user) or only the current one (the default):

claude mcp add --scope user ttc -- uv run --project /absolute/path/to/ttc-mcp ttc-mcp --transport stdio

Claude Desktop, in claude_desktop_config.json (on macOS in ~/Library/Application Support/Claude/, on Windows in %APPDATA%\Claude\). Give the full path to uv (from which uv, or where uv on Windows), because the desktop app usually doesn't see your shell's PATH:

{
  "mcpServers": {
    "ttc": {
      "command": "/absolute/path/to/uv",
      "args": ["run", "--project", "/absolute/path/to/ttc-mcp", "ttc-mcp", "--transport", "stdio"]
    }
  }
}

A local stdio server checks the City's portal when it starts, builds a new timetable first if the TTC has published one (the start waits for it), and checks again every 24 hours while it runs. Building before you connect, as above, keeps the first start quick; if your client gives up waiting on the day a new timetable appears, run uv run ttc-mcp-build in a terminal and reconnect. Add --no-refresh to the arguments to skip the check, for example offline.

Locally, over HTTP

uv run ttc-mcp --transport http --host 127.0.0.1

The server runs in the foreground. Then, in another terminal:

claude mcp add --transport http ttc http://127.0.0.1:8080/mcp

The server speaks streamable HTTP at /mcp with stateless JSON responses. Like the stdio server it checks the City's portal when it starts, but it doesn't recheck; run uv run ttc-mcp-build to refresh the timetable, and the running server picks it up on its next request. Without Google sign-in configured it is open to anyone who can reach it, and it says so when it starts: --host 127.0.0.1 keeps it on your machine, while the default, 0.0.0.0, listens on every network interface.

Docker

The image is the one Cloud Run runs, and building it needs Docker with BuildKit (the default since Docker 23). It serves HTTP on port 8080 and keeps its timetable in /data, so give it a volume and build the timetable into it first:

docker build -t ttc-mcp .
docker volume create ttc-mcp-data
docker run --rm -v ttc-mcp-data:/data ttc-mcp /app/.venv/bin/ttc-mcp-build
docker run --rm -p 127.0.0.1:8080:8080 -v ttc-mcp-data:/data ttc-mcp

The server is then at http://127.0.0.1:8080/mcp. The container never checks the City's portal by itself: run the build command again to refresh the timetable (it does nothing when the TTC hasn't published a new one), and the running server picks the new one up on its next request. Call the installed commands under /app/.venv/bin, not python -m ttc_mcp..., which inside /app would load the source copy instead of the installed package. -e TTC_MCP_NOW=2026-10-06T08:00 pins the clock the server uses for timetables and closure windows, for a date a loaded timetable covers; live feeds still answer with today's data.

On your own Linux machine

The maintainer's copy runs this way: two systemd user units, the HTTP server on loopback behind Google sign-in and a daily timer that runs ttc-mcp-build instead of a cloud Job, published over HTTPS through a Cloudflare Tunnel. Secrets sit in their own files (GOOGLE_OAUTH_CLIENT_SECRET_FILE, JWT_SIGNING_KEY_FILE), and the OAuth state stays on disk, encrypted, so a restart doesn't sign clients out. docs/self-host.md has the unit files, the tunnel, the checks and a note on keeping a WSL distro running.

Google Cloud Run

The hosted shape, specified in docs/spec.md section 6.9, uses the same image twice (the maintainer's copy ran this way until 2026-11-01):

  • A Cloud Run service runs the server over HTTPS behind Google sign-in, scaling between zero and one instance. At startup and every six hours it pulls the timetable from a Cloud Storage bucket; it never contacts the City's portal itself.

  • A Cloud Run Job, started daily at 04:30 Toronto time by Cloud Scheduler, runs ttc-mcp-build --bucket: it pulls the bucket's timetable, checks the City's portal, and builds and pushes a new timetable only when the TTC has published one.

  • Google sign-in runs inside the server: FastMCP's Google OAuth provider with dynamic client registration, then an email allowlist checked on every request. The OAuth state is kept, encrypted, in Firestore so sign-ins survive scaling to zero, and its three secrets live in Secret Manager.

For one person this is sized to stay within Google Cloud's free tiers (the estimate is in spec 6.9); check your project's billing before you start. docs/cloud-run.md is the build specification for the hosted copy: what gets provisioned, how it is sized now that the trip planner keeps its index in memory, the order deploys must follow when the timetable format changes, and the checks that say it works.

docs/deploy.md is the complete runbook, one block of commands per step: billing headroom, APIs, service accounts, the bucket, the secrets, the OAuth client, Firestore, the image, the Job (run once so the bucket has a timetable), the service, the scheduler, then the connectors and the post-deploy checks. It ran end to end on 2026-10-18, and the fixes that run needed are in it. It is written with this project's names, region and allowlist address, uses pbpaste (macOS) to keep secrets out of the terminal, and assumes the project already has an OAuth consent screen; substitute yours.

Once it is deployed, connect a client to https://<your-service-url>/mcp:

  • claude.ai: Settings, Connectors, Add custom connector. Paste the URL, leave the advanced OAuth settings empty, and sign in with an allowlisted Google account; the Claude apps share the connector.

  • Claude Code: claude mcp add --transport http ttc https://<your-service-url>/mcp, then sign in from /mcp.

Configuration

The server reads these environment variables, and ttc-mcp-build reads TTC_MCP_CACHE, TTC_MCP_UA and TTC_MCP_STATIC_BUCKET. CLAUDE.md has the same table with the module that reads each one.

Variable

Default

What it does

TTC_MCP_TRANSPORT

stdio (the image sets http)

The transport when --transport isn't given

PORT

8080

The HTTP port when --port isn't given; Cloud Run sets it

TTC_MCP_CACHE

~/.cache/ttc-mcp (the image sets /data)

Where the timetable files, their manifest.json and the build's downloads live

TTC_MCP_UA

ttc-mcp/<version> (+https://github.com/LunaParker/ttc-mcp)

The User-Agent on every request; several TTC servers refuse Python's default. If you run a copy, set your own contact here

TTC_MCP_SKIP_REFRESH

off (the image sets 1)

1, true or yes skips the startup portal check and the 24-hour recheck

TTC_MCP_STATIC_BUCKET

unset

HTTP server: pull the timetable from this Cloud Storage bucket at startup and every TTC_MCP_PULL_S seconds, and never check the portal. Build: pull the bucket's timetable first and push the result after. Needs the cloud extra (uv sync --extra cloud) and Google Cloud credentials

TTC_MCP_PULL_S

21600 (6 hours)

How often the HTTP server pulls the bucket; values under 60 are refused

TTC_MCP_NOW

unset (the Toronto clock)

Pins the clock used for timetable lookups and closure windows to an ISO 8601 time, read as Toronto time when it has no offset; live feeds still answer with today's data

GOOGLE_OAUTH_CLIENT_ID

unset (no sign-in)

Turns on Google sign-in for the HTTP server; the next four become required, and a half-configured gate refuses to start

GOOGLE_OAUTH_CLIENT_SECRET

The OAuth client's secret

BASE_URL

The service's public origin; the OAuth redirect URI is BASE_URL/auth/callback

JWT_SIGNING_KEY

Signs the tokens the server issues

ALLOWED_EMAILS

The Google accounts allowed in, comma-separated; an empty list refuses to start

GOOGLE_OAUTH_CLIENT_SECRET_FILE, JWT_SIGNING_KEY_FILE, STORAGE_ENCRYPTION_KEY_FILE

unset

A file holding that secret, read and trimmed at startup, instead of the variable itself (never both); an unreadable or empty file refuses to start

GOOGLE_CLOUD_PROJECT

unset (OAuth state in encrypted files under FASTMCP_HOME)

Keeps the OAuth state in this project's Firestore database

STORAGE_ENCRYPTION_KEY

The Fernet key that encrypts that state; required with GOOGLE_CLOUD_PROJECT

FASTMCP_HOME

FastMCP's per-user data directory

Read by FastMCP: without Firestore the gate's OAuth state lives here, encrypted with a key derived from JWT_SIGNING_KEY

Command options:

Command

Option

Effect

ttc-mcp

--transport stdio|http

stdio for a local client, HTTP for a network one

--host, --port

Where HTTP listens; defaults 0.0.0.0 and PORT or 8080

--no-refresh

Don't check the City's portal at startup or every 24 hours

ttc-mcp-build

--force

Rebuild even when the portal's files haven't changed

--cache DIR

Build somewhere other than TTC_MCP_CACHE

--from-zip FILE, --surface-zip FILE

Build from GTFS zips on disk instead of the portal

--no-surface

Skip the surface feed; vehicle positions then can't be matched to public stop numbers

--no-ttcapi

Skip ttc.ca's route service levels (one request per route)

--no-checks

Skip the data checks logged after each build

--bucket NAME

Pull this bucket's timetable before the portal check and push the result after, as the Job does; with --from-zip it pushes without pulling and replaces the bucket's timetable list. Needs the cloud extra and Google Cloud credentials

Data sources and licences

ttc-mcp reads two kinds of TTC data: open data the TTC publishes on the City of Toronto's portal, and endpoints behind the TTC's own website.

Data

Source

Kind

Timetables: routes, stops, schedules, shapes and station layouts

Merged GTFS - TTC Routes and Schedules

Open data

Bus and streetcar stop ids used by the live feeds

Surface Routes and Schedules for BusTime

Open data

Vehicle positions, crowding and trip updates

bustime.ttc.ca/gtfsrt, listed as TTC BusTime Real-Time Next Vehicle Arrival (NVAS)

Open data

Service alerts with closures' nightly windows, detours, and subway trip updates

gtfsrt.ttc.ca, listed as TTC GTFS-Realtime (GTFS-RT) Service Disruptions

Open data

Bus and streetcar predictions at a stop

the BusTime web map, bustime.ttc.ca/bustime/map/getStopPredictions.jsp

Behind the TTC's website

Subway and LRT predictions

NTAS, ntas.ttc.ca

Behind the TTC's website

Closures with their spans and shuttles, reduced speed zones, elevator and escalator outages

alerts.ttc.ca

Behind the TTC's website

Route service levels and service changes

www.ttc.ca/ttcapi

Behind the TTC's website

Open data. The TTC publishes the four portal datasets, and their pages on the portal give the Open Government Licence – Toronto; the portal's API leaves their licence field unspecified. The licence lets you copy, modify, publish and distribute the information for any lawful purpose, commercial included, on a few conditions: credit the source with the attribution statement below and, where possible, link to the licence; don't suggest that the City or the TTC endorses you or your use; it grants no rights to personal information, third parties' rights, official names, crests, logos or trademarks; the information comes as is, without warranty; and the rights end automatically if you don't meet the conditions. ttc-mcp's attribution, which the server also sends to every client in its instructions, with the licence link:

Contains information licensed under the Open Government Licence – Toronto.

Endpoints behind the TTC's website. Stop predictions, train predictions, alerts.ttc.ca and ttc.ca's route data aren't open data: they are what the TTC's own website and BusTime map call, and the TTC publishes no terms specific to them. Its website terms of use let you view and copy material from its site for your own personal, non-commercial use if you leave its copyright, trademark and other notices in place; reserve selling or modifying that material, and republishing, reproducing, displaying or distributing any substantial part of it, to the TTC's prior written permission; ask for the source to be acknowledged whenever TTC material is copied or published; and disclaim its accuracy. They don't say whether they reach these endpoints. ttc-mcp calls them on demand, as the website does, and adds a cache, a User-Agent naming the project and backoff when they fail, to answer one person's own trip questions. It is built for that personal, non-commercial use; whether other uses are allowed is for the TTC to say, and the portal lists opendata@ttc.ca as its open-data contact. The TTC can change or withdraw these endpoints at any time, which is why the answers built on them are marked best effort.

This section describes the terms as published on 12 October 2026, and it isn't legal advice.

Not affiliated. ttc-mcp is an independent project. It is not affiliated with, endorsed by or supported by the Toronto Transit Commission or the City of Toronto, whose names and marks belong to them.

Licence

ttc-mcp's code is released under the MIT License. The licence covers the code only: the data it reads stays under its publishers' terms, described above, whoever runs it. The Python packages it depends on keep their own licences: MIT, BSD and Apache 2.0 for most of them, ISC, PSF 2.0, MIT-0 and the Unlicense for a few, and MPL 2.0 for the certifi certificate bundle.

Development

Contributor documentation lives beside the code:

uv sync --extra cloud                  # dev tools, plus the cloud extra the unit suite needs
uv run pytest                          # the unit suite: offline, on a small fixture timetable
uv run ruff check .                    # lint
uv run pytest tests/integration        # live: boots the server and asks the TTC; needs a timetable (uv run ttc-mcp-build)
TTC_MCP_SKIP_REFRESH=1 uv run python scripts/smoke_derived.py   # one of the live smoke scripts in scripts/
TTC_MCP_SKIP_REFRESH=1 uv run python scripts/validate_connections.py   # the 20 trips connections is judged on
TTC_MCP_SKIP_REFRESH=1 uv run python scripts/bench_raptor.py   # the trip planner's 250 ms latency budget
scripts/smoke_image.sh                 # the container smoke test (needs Docker)

Every pull request runs four CI jobs: the unit suite, lint, a package build and the container smoke test.

Disclaimer

ttc-mcp comes with no warranty of any kind and makes no claim that its answers are accurate, complete or current. They are built from the TTC's timetables and live feeds, which can be wrong or late: a predicted arrival or train time (NTAS's included), a crowding level, a closure, a detour, an elevator's status or a planned trip may not match what is actually happening. Pay attention to what you see and hear at the stop or station, and to the TTC's own signs, announcements and staff. Don't rely on ttc-mcp in an emergency, or wherever a wrong or missed connection could cause harm. To the extent the law allows, its author accepts no liability for any loss or harm from using it, as the MIT License also says.

Available Tools

19 tools
accessibilityAccessibilityA

Accessibility of a station (id, name or alias) or a stop: platform and entrance wheelchair flags, the station summary, elevator and escalator units with their levels and live status (alerts joined to pathway nodes by unit code; codes without a node are reported with node_match false), uncoded node counts and a step-free verdict with its basis. Outages announced but not yet active are listed under upcoming and leave the units and the verdict alone. A platform or entrance id answers for its station, with queried_platform or queried_entrance naming what was asked.

NameRequiredDescriptionDefault
queryYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior5/5

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

With no annotations provided, the description carries the full behavioral burden and does so well. It discloses how alerts are joined to pathway nodes, that unmatched codes are reported with `node_match false`, that uncoded node counts are included, that upcoming outages are listed separately and do not affect units or the verdict, and that platform/entrance queries answer for their station via `queried_platform` or `queried_entrance`.

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 dense and front-loads the core purpose before detailing return semantics. It is longer than typical, but nearly every clause carries useful behavioral or parameter context, with only mild run-on density that could be improved with clearer separation.

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?

Given a one-parameter tool with an output schema, the description is largely complete: it explains what the query accepts and what kinds of accessibility information are returned. It does not cover error handling or explicitly route the agent away from sibling tools, but nothing essential for correct invocation is missing.

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 input schema has zero description coverage for the single `query` parameter, so the description must compensate. It does by explaining that the query accepts a station id, name, or alias, or a stop, and that platform or entrance ids are also valid and answered at station level. It could still be stronger with format examples, but it adds substantial 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 identifies the resource as station/stop accessibility data and enumerates the specific contents: wheelchair flags, station summary, elevator/escalator units, node matching, and step-free verdict. It is more specific than the generic `station` or `stop` siblings, though it does not explicitly name those alternatives.

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?

Usage is implied by the purpose: call this when accessibility details are needed for a station or stop. However, there is no explicit when-to-use guidance, no when-not-to-use guidance, and no direct comparison to sibling tools such as `station`, `stop`, or `alerts`.

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

alertsAlertsA

Current TTC alerts as normalised elements: subway closures (with nightly windows and shuttle info), surface suspensions and detours (alerts and the TripModifications feed), reduced speed zones, elevator and escalator outages. Filter by route, station (id, name or alias), stop number or type. Stations and stops are resolved to objects; times are America/Toronto. Answers are best effort, from the TTC's own site endpoints.

NameRequiredDescriptionDefault
stopNo
typeNo
routeNo
stationNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior4/5

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

With no annotations, the description carries the full burden and does meaningful work: it discloses the data source ('the TTC's own site endpoints'), flags reliability as 'best effort', specifies the timezone (America/Toronto), and notes that stations/stops are resolved to objects and that nightly windows and shuttle info are included. It omits failure modes, rate limits or freshness/caching behaviour.

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?

Three tight sentences: what it returns, how to filter, then operational caveats (resolution, timezone, best effort). Everything is front-loaded and the parenthetical asides are genuinely informative rather than filler.

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?

An output schema exists, so return-value shape need not be restated, and the description covers scope, filters, timezone and reliability. The remaining gap is combinability of filters and the meaning of the unlisted 'type' enum members, which matters for a zero-coverage schema.

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

Parameters3/5

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

Schema coverage is 0%, so the description must compensate, and it does partially: it names all four filters (route, station with 'id, name or alias', stop number, type). However, it never maps the 'type' parameter to its eight enum values, omits 'delay' and 'other' entirely, and says nothing about whether filters are optional or combined with AND.

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?

States a concrete resource ('Current TTC alerts') and enumerates the alert categories returned (subway closures, surface suspensions/detours, reduced speed zones, elevator/escalator outages), so an agent knows exactly what comes back. It never names the related siblings (detours, service_status, service_changes), so the differentiation is left to inference rather than stated.

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?

Usage is implied by 'current alerts' and the filter list, but there is no explicit when-to-use guidance and no exclusion pointing at siblings like detours or service_status that cover overlapping ground. An agent must infer that this is the umbrella alerts feed rather than the narrower ones.

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

arrivalsArrivalsB

Next vehicles at a bus or streetcar stop (BusTime predictions, crowding from the vehicle feed) or next trains at a rail platform (NTAS). Rows carry basis realtime or schedule: when no real-time prediction exists the timetable fills in and a note says so. A rail platform inside an active closure answers no arrivals at all (NTAS keeps predicting there) with a note and the closure. Every row carries detour_id (the vehicle's trip is on a detour) and the answer lists the stop's disruptions. Answers are best effort, from the TTC's own site endpoints; historical occupancy is out of scope.

NameRequiredDescriptionDefault
stopYes
limitNo
routeNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.3/5.0
Behavior4/5

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

Without annotations, the description discloses several behavioral traits: basis (realtime/schedule) with notes, closure handling, detour_id per row, disruption listing, best-effort sourcing from TTC endpoints, and that historical occupancy is out of scope. It does not cover auth or rate limits, but adds substantial context beyond schema.

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 a single, well-structured paragraph with the core purpose front-loaded. Each sentence adds value (edge cases, data basis, detour info, limitations) without excessive verbosity.

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 no annotations, 0% schema coverage, and the presence of an output schema, the description covers behavioral aspects well but omits parameter semantics and explicit usage guidance, leaving gaps for correct invocation.

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

Parameters1/5

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

Schema description coverage is 0%, so the description must compensate, but it provides no explanation of the 'stop', 'limit', or 'route' parameters. No format, expected values, or effects are described.

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 returns next vehicles at a bus/streetcar stop or next trains at a rail platform, with real-time or schedule basis. It distinguishes itself from general vehicle tracking but does not explicitly name alternatives like next_trains or scheduled_departures.

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?

Implies usage for arrivals at a stop/platform, but provides no explicit when-to-use or when-not-to-use guidance relative to siblings. No alternatives are named.

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

connectionsConnectionsA

Timetable-exact trips between two places on the TTC: every ride is a real trip with its departure and arrival, transfers are timed (walking or station pathways plus a 2-minute change buffer), and the options are the Pareto set over arrival time and transfers, ranked by arrival. origin and destination are a stop number, a station id or alias, or {"lat", "lon"} from a geocoder. depart_at (ISO, Toronto when it has no offset; default now) with window_min (0 to 60, default 30) returns the journeys found every five minutes across the window (every ten when window_min is over 30), one per route sequence; arrive_by instead returns the latest departures that arrive on time, latest first, the first within five minutes of the latest that does; give one or the other. max_transfers is 0 to 3, walk_m (metres to and from the stops) 1 to 2000, limit 1 to 10. Each ride carries basis realtime (an update was applied to its trip, within an hour of now), schedule, or estimate; the first ride lists the next departures in its direction. A closure masks the closed section; when its shuttle runs the shuttle is an estimated leg (assumed headway and speed, stated in its note) and the option is flagged estimate: true and ranked last. A detoured route's trips skip their stops with the published delay. Every option lists the disruptions it touches with the legs they affect, and an empty answer says why. Closures are judged at the query instant, so the note names one that starts or ends inside the search. Live departures and disruptions are best effort, from the TTC's own site endpoints.

NameRequiredDescriptionDefault
limitNo
originYes
walk_mNo
arrive_byNo
depart_atNo
window_minNo
destinationYes
max_transfersNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations, the description carries the whole burden and does so: it discloses the sampling cadence (every five minutes, ten past a 30-minute window), the Pareto ranking over arrival and transfers, the basis taxonomy (realtime/schedule/estimate) and the one-hour realtime freshness bound. It also anticipates failure modes — closures masking a section, shuttle legs assumed rather than timed and ranked last with estimate: true, detours carrying published delays, and empty answers stating why — plus that live data is best effort.

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?

Dense but justified — the length is bought by eight undocumented parameters and rich behavioral caveats, and the purpose is stated in the first clause before detail. It loses a point for being a single run-on paragraph where parameter ranges are buried mid-prose, which makes scanning harder than it needs to be.

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

Completeness5/5

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

For an 8-parameter tool with no annotations and no schema descriptions, this is complete: it covers valid inputs, defaults, mutual exclusion, ranking, live-data caveats and degradation paths. Return-value explanation is not needed because an output schema exists, yet it still tells the agent what each ride and option carries (basis, disruptions per leg, first-ride next departures).

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?

Schema description coverage is 0%, so the description must carry everything, and it does: it documents the four accepted origin/destination forms (stop number, station id, alias, {"lat","lon"} from a geocoder), the ISO timezone fallback for depart_at, and explicit ranges for window_min (0-60), max_transfers (0-3), walk_m (1-2000 m) and limit (1-10). It also explains the semantic difference between depart_at and arrive_by rather than just their types.

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?

Opens with a specific verb and resource: 'Timetable-exact trips between two places on the TTC,' and immediately qualifies the output as real trips with departure/arrival, not schedules or stop lists. No sibling tool performs point-to-point journey planning, so an agent can distinguish it from find_stops, scheduled_departures or next_trains without opening a schema.

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

Usage Guidelines4/5

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

Gives an explicit either/or for the two mutually exclusive time modes ('give one or the other') and states what each returns, which is exactly the decision an agent has to make. It stops short of naming when to prefer a sibling (e.g. scheduled_departures for a single stop), so it is clear context without explicit sibling routing.

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

detoursDetoursB

Active surface detours from the TTC's TripModifications feed, grouped by detour id: per pattern the skipped stops, where the vehicle leaves and rejoins the route, the detour path (encoded polyline and length), trips affected, service dates, and the reason when a service change matches (null otherwise). Answers are best effort, from the TTC's own site endpoints.

NameRequiredDescriptionDefault
routeNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior4/5

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

With no annotations, the description carries the burden and adds real signal: it discloses the upstream data source (TTC's own site endpoints), the 'best effort' reliability caveat, the grouping key, and null semantics for the reason field. It omits freshness/refresh expectations and whether results are cached, which keeps it short of a 5.

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?

Resource and scope are front-loaded in the first clause, and the second sentence delivers the data-quality caveat without padding. The middle enumeration is dense but every item maps to real returned content.

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?

The output schema exists, so return values need no prose explanation, and the data-source caveat is covered. However, for a tool with no annotations and a completely undocumented sole parameter, the description should say how 'route' filters results and when to prefer alerts/service_changes.

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

Parameters2/5

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

Schema description coverage is 0% for the single 'route' parameter, and the description never mentions it — no explanation of accepted route identifier format, whether it narrows to one route, or what omitting it returns. That leaves the only input entirely undocumented across both prose and 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 names a specific resource (active surface detours from the TTC TripModifications feed) and enumerates the per-detour payload, so the agent knows exactly what comes back. It does not explicitly distinguish itself from siblings like alerts or service_changes, though the 'service change' mention gives a partial hint at the boundary.

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?

There is no statement of when to call this instead of alerts or service_changes, nor any prerequisites or exclusions. The phrase 'Active surface detours' implies the use case, but an agent comparing three similarly-themed siblings gets no routing guidance.

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

feed_statusFeed StatusA

Freshness of the static feed and of every real-time source as cached in this process (TTL state, age, feed timestamp, last error, NTAS coverage per line). probe=true makes one live request per source.

NameRequiredDescriptionDefault
probeNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior4/5

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

With no annotations, the description carries the full burden and does reasonably well: it explains the tool reads process-cached state and, critically, discloses that probe=true triggers one live request per source (a network side effect with likely latency). It does not mention any cost/timeout of probing, and read-only nature is only implied, keeping it below a 5.

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?

Two compact sentences with no filler; the freshness scope is front-loaded and the probe rule is placed last as an actionable modifier. The dense parenthetical of internal fields is slightly hard to parse but every element is substantive, so no waste.

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?

An output schema exists, so return-value explanation is not required, and the description appropriately focuses on scope and the probe side effect. For a simple 1-parameter diagnostic tool this is nearly complete; the only missing piece is usage context, which is scored separately under guidelines.

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?

Schema description coverage is 0%, so the description must explain the single parameter, and it does: 'probe=true makes one live request per source,' which conveys the difference between cached (default) and live behavior. It does not explicitly restate the false/default path, but the cached framing implies it, landing just short of full coverage.

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 specifies the resource precisely: freshness/health of the static feed plus every real-time source, with concrete reported fields (TTL state, age, feed timestamp, last error, NTAS coverage). It is clearly distinguishable from all transit-data siblings (stops, routes, arrivals, etc.). It loses a point only because it leads with a noun phrase rather than an explicit verb, and 'NTAS' is unexplained jargon.

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?

There is no statement of when to use this tool versus alternatives like service_status or service_changes, nor any condition such as 'call when data appears stale/before trusting cached results.' The probe=true note is invocation detail, not usage guidance. An agent must infer the when-to-use context.

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

find_stopsFind StopsB

Find TTC stops, platforms or stations by name, stop number, station name or alias (e.g. "Dundas" resolves to TMU). Stops are directional: the two sides of an intersection are different stop numbers with different headsigns.

NameRequiredDescriptionDefault
modeNo
limitNo
queryYes
routeNo
directionNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.3/5.0
Behavior3/5

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

No annotations are provided, so the description carries the full burden. It does disclose a genuinely non-obvious domain trait — that stops are directional and the two sides of an intersection are separate stop numbers with different headsigns — which prevents a real class of errors, but says nothing about result volume, default limit behavior, or whether the search is fuzzy or exact.

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?

Two sentences, both load-bearing: the first defines the operation and inputs, the second delivers the directionality caveat. Front-loaded with zero padding.

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?

Return values are covered by the output schema, and the query/direction concepts are addressed, but for a 5-parameter tool with no annotations and no schema descriptions, the filtering parameters (mode, route, limit) remain undefined in every layer an agent can see.

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

Parameters2/5

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

Schema description coverage is 0% across 5 parameters. The description explains only `query` (accepted key types plus an alias example); `mode`, `limit`, `route` and `direction` are never mentioned, and the enum values '0'/'1' for direction are not connected to the headsign/directionality sentence.

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?

States a specific verb ('Find') and resource ('TTC stops, platforms or stations') plus the lookup keys (name, stop number, station name, alias). An agent can infer this is a text-search over stops rather than a proximity lookup, but the description never names nearest_stops or stop to make the boundary explicit.

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 alias example ('Dundas' resolves to TMU) gives a usable hint about input expectations and implicitly distinguishes this from geographic search via nearest_stops, but there is no explicit when-to-use/when-not guidance or named alternative across the 18 sibling tools.

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

nearest_stopsNearest StopsB

Stops and stations nearest to a coordinate (geocode addresses with a maps tool first), with walk minutes, the routes, directions and headsigns served, and active disruptions per result.

NameRequiredDescriptionDefault
latYes
lonYes
kindNoany
limitNo
radius_mNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior3/5

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

No annotations exist, so the description carries the full burden. It does usefully disclose the per-result payload (walk minutes, routes, directions, headsigns, active disruptions) and implies a proximity-ordered read, but says nothing about empty-result behavior, radius-outside behavior, rate limits, or auth, which matter for a live transit API.

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?

One dense sentence, front-loaded with the core action and with the geocoding caveat in parens where it's scannable. Slightly run-on at the tail enumerating return fields, but no dead weight.

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?

An output schema exists, so return values needn't be explained, and the description covers purpose plus ranking input. But for a five-parameter tool with zero schema documentation and no annotations, the absence of any mention of radius, limit, or kind filtering leaves the agent guessing how to tune the search.

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

Parameters2/5

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

Schema description coverage is 0% across five parameters. The description only implies lat/lon and loosely gestures at `kind` via 'stops and stations'; it never explains `radius_m` (default 500), `limit` (default 10), or how `kind` filters results — the three parameters most likely to be mis-set.

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?

States a specific verb+resource with scope: stops and stations nearest a coordinate. That proximity framing is distinct from the name-oriented siblings (find_stops, stop, station) and lets an agent pick it without opening schemas, but it never names those siblings explicitly.

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

Usage Guidelines4/5

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

Gives a real precondition — geocode addresses with a maps tool first — which prevents a whole class of failed calls. It stops short of stating exclusions or naming find_stops as the alternative for name-based lookup, so it's clear context rather than full when/when-not guidance.

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

next_trainsNext TrainsA

Next trains at every rail platform of a station (id, name or alias such as "Dundas" or "Bloor") or at one platform id, from the TTC's NTAS. A platform with no prediction falls back to the timetable (service no_prediction); an empty NTAS answer never means no trains. A platform inside an active closure is service suspended with the closure and no minutes (next_min [], no basis, NTAS's own numbers in ntas_min, no timetable); a closure's end station keeps real minutes on the platform facing the open line. Answers are best effort, from the TTC's own site endpoint.

NameRequiredDescriptionDefault
queryYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.1/5.0
Behavior4/5

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

With no annotations, the description carries the full behavioral burden and does well by explaining the timetable fallback, that an empty NTAS answer does not mean no trains, and how active closures affect predictions. It does not discuss authentication, rate limits, or explicit read-only status, but it provides strong edge-case transparency.

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 front-loaded with the core purpose and then explains relevant fallback and closure semantics. It is dense but mostly earns its length, though the parenthetical line breaks and nested caveats make it slightly harder to scan than ideal.

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

Completeness5/5

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

For a one-parameter read tool with an output schema, the description covers the important behavioral context: station versus platform queries, NTAS fallback, empty-response meaning, and closure semantics. An agent can invoke it correctly without additional explanation.

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 schema gives only a bare string for query, so the description adds necessary meaning by saying it accepts a station id, name, or alias such as "Dundas" or "Bloor", or a single platform id. It stops short of documenting platform-id format or validation rules, but it compensates well for the 0% schema description coverage.

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 that the tool returns next trains at every rail platform of a station or at a single platform id, sourced from TTC's NTAS. The verb and resource are specific, though it does not explicitly distinguish itself from nearby siblings such as arrivals or scheduled_departures.

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

Usage Guidelines4/5

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

It explains the two main usage modes: querying by station id, name, or alias, or querying by a single platform id. It also clarifies fallback and closure behavior, but it does not explicitly say when to use this tool instead of sibling tools like arrivals or scheduled_departures.

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

routesRoutesA

All TTC routes with mode (subway, lrt, streetcar, bus), series (rail, bus, express, blue-night, community, streetcar), service hours by day type and overnight share. overnight=true lists the Blue Night network.

NameRequiredDescriptionDefault
modeNo
seriesNo
overnightNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

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

No annotations exist, so the description carries the full burden. It discloses the returned fields and the special overnight semantics, which is real context, but says nothing about result size, pagination, filter combination logic, or ordering for what is likely a large unfiltered list.

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?

Two compact sentences with no filler, and the enumeration of available data is front-loaded before the overnight special case. The parenthetical value lists make it dense but they earn their space given 0% schema coverage.

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?

An output schema exists, so return structure needn't be explained, and all three parameters receive semantic treatment. What remains missing is filter-interaction behavior and volume expectations for an unfiltered call, which matters for a catalog of this size.

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?

Schema coverage is 0%, so the description must compensate — and it largely does, enumerating the accepted mode values (subway, lrt, streetcar, bus) and series values (rail, bus, express, blue-night, community, streetcar) and explaining overnight. The only gap is that it never states whether mode, series and overnight are AND-combined or independent.

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?

Opens with a specific verb+resource ("All TTC routes") and enumerates the attributes returned (mode, series, service hours by day type, overnight share), so an agent knows this is the route catalog. It does not name any sibling (route_stops, service_hours, find_stops) to draw the boundary, but the resource is distinct enough to be actionable.

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?

"overnight=true lists the Blue Night network" is the only conditional guidance and it is genuinely useful. There is no statement of when to reach for this tool versus service_hours or route_stops, and no mention of whether filters combine or what happens with no filters at all.

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

route_shapeRoute ShapeA

The geometry of a route and direction: the most-used stop pattern's shape (or another pattern by rank) as an encoded polyline or GeoJSON LineString, with its stops and their distance along the shape. Optional Douglas-Peucker simplification (metres) and a from_stop/to_stop span sliced by shape_dist_traveled (the first visit of each when a stop repeats).

NameRequiredDescriptionDefault
routeYes
formatNopolyline
patternNo
to_stopNo
directionYes
from_stopNo
simplify_mNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral burden and does well: it explains output formats, Douglas-Peucker simplification, pattern ranking, and span slicing semantics including first-visit behavior for repeated stops. It does not explicitly state read-only behavior or permission requirements, leaving a minor gap.

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 dense but front-loaded around the core return value and then adds important optional behavior. The single long sentence is efficient, though the packed clause structure could be slightly harder to scan.

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?

An output schema exists, so return-value structure does not need full explanation. Still, for a complex 7-parameter tool with no annotations and 0% schema coverage, the description leaves required-parameter meaning and usage routing insufficiently covered.

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?

Schema description coverage is 0%, so the description must compensate, and it does for most parameters: format, pattern rank, simplify_m, and from_stop/to_stop semantics are explained. However, it does not clarify the required route identifier format or what direction values '0' and '1' mean, leaving key required-parameter semantics implicit.

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 states precisely what the tool returns: a route direction's most-used stop pattern geometry as polyline or GeoJSON, with stops and distance along the shape. It distinguishes itself from siblings like route_stops by describing the geometric shape plus stop distances rather than just an ordered list of stops.

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?

Usage is implied by the geometry-focused description, but there is no explicit when-to-use or when-not-to-use guidance relative to siblings such as route_stops or stop. The agent can infer context but must decide on its own when this tool is preferable.

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

route_stopsRoute StopsB

Ordered stops for a route and direction, grouped by stop pattern (branches, short turns) with trip counts, headsigns, typical minutes from the first stop, and a status per stop: open, closed (station closure) or skipped_by_detour.

NameRequiredDescriptionDefault
routeYes
patternNo
directionYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3/5.0
Behavior3/5

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

No annotations exist, so the description carries the full burden. It does disclose the result shape and the meaning of the status values (open, closed due to station closure, skipped_by_detour), which is genuine behavioral context, but it says nothing about read-only nature, error behavior for unknown routes, or why branches are grouped separately.

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?

A single dense, front-loaded sentence with no filler; the returned fields are enumerated efficiently. It is slightly over-packed into one sentence, which makes the scoping and the output catalog harder to scan separately.

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?

An output schema exists, so the description needn't re-describe returns, yet it spends its words there while leaving the two required parameters and the optional pattern parameter undocumented. For a 3-parameter tool with zero schema descriptions, that leaves the caller guessing on direction encoding.

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

Parameters2/5

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

Schema coverage is 0%, so the description must compensate and it largely doesn't: it never explains the format of 'route', never states what direction value '0' versus '1' means, and never mentions the optional 'pattern' parameter at all despite pattern grouping being central to the output.

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?

States clearly what is returned: ordered stops for a given route and direction, grouped by stop pattern, with trip counts, headsigns, typical minutes, and per-stop status. This distinguishes it implicitly from find_stops/nearest_stops (name-based or proximity lookup) by its route+direction scoping, but it never names a sibling or explicitly contrasts the scope.

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?

There is no guidance on when to use this versus find_stops, nearest_stops, stop, or route_shape, nor on prerequisites such as needing a valid route identifier. The agent must infer the use case purely from the returned-field list.

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

scheduled_departuresScheduled DeparturesA

Timetable departures at a stop from a given time (default now), including previous-service-day trips that run past midnight. Schedule only; use arrivals for real time.

NameRequiredDescriptionDefault
atNo
stopYes
limitNo
routeNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations, the description carries the burden and discloses the key behavioral traits: this is a scheduled/timetable view, not real-time, the time parameter defaults to now, and past-midnight previous-day trips are included. Auth, rate limits, and pagination behavior for limit are not covered, which keeps it from a 5.

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?

Two sentences, zero filler, with the core purpose and the schedule-vs-real-time distinction front-loaded before the edge case.

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?

An output schema exists, so return values need no explanation, and the description covers the nontrivial traps (real-time confusion, past-midnight trips, default time). The undocumented limit and route parameters are the remaining gap.

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

Parameters3/5

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

Schema description coverage is 0%, so the description must compensate for all four parameters. It explains stop and adds meaning beyond the schema for at ("default now" versus the schema's null), but route and limit are never mentioned even though both affect results.

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?

States a specific verb+resource (timetable departures at a stop) with scope (from a given time, default now) and an edge case (previous-service-day trips running past midnight). The contrast with arrivals makes it unmistakable among siblings such as next_trains or connections.

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

Usage Guidelines4/5

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

"Schedule only; use arrivals for real time" gives an explicit when-not and names the alternative, which is exactly the confusion an agent would have in this sibling set. It stops short of routing among the other boarding-related siblings (next_trains, connections, service_hours).

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

service_changesService ChangesA

Board-period route changes: GTFS-RT MODIFIED_SERVICE alerts (title, text, period, url, routes) merged with ttc.ca's service-changes list when published. Filter by route and by a date the change must cover. Answers are best effort, from the TTC's own site endpoints.

NameRequiredDescriptionDefault
dateNo
routeNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

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

With no annotations, the description carries the transparency burden and usefully discloses merged upstream sources, conditional ttc.ca publication, and best-effort reliability. It still omits explicit read-only status, authentication/rate-limit expectations, and failure/empty-result behavior beyond 'best effort'.

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 compact and front-loaded: source composition, filters, and reliability caveat appear in order. Every sentence contributes useful information without repetition.

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?

An output schema exists, so return-value documentation is not required here. The description covers sources, merge behavior, filters, and best-effort caveats, but it could be more complete by specifying parameter formats and how this tool relates to nearby alert/detour tools.

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

Parameters3/5

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

Schema description coverage is 0%, so the description must explain both parameters. It does give meaning to both: route filtering and a date that the change must cover. It does not specify expected date or route formats, leaving some ambiguity.

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 identifies the resource and sources: board-period service changes built from GTFS-RT MODIFIED_SERVICE alerts and ttc.ca's service-changes list. It is specific about what the tool returns, but it does not explicitly distinguish itself from siblings such as alerts, detours, or service_status.

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?

It explains the available filters (route and date coverage) and notes that results are best effort, which implies usage for board-period route changes. However, it gives no explicit when-to-use/when-not guidance or alternatives versus alerts, detours, or service_status.

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

service_hoursService HoursB

Whether a route runs on a date (calendar plus holiday exceptions) and its first and last trip times. GTFS times past 24:00 belong to the previous service day.

NameRequiredDescriptionDefault
dateNo
routeYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.1/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. It usefully discloses the GTFS-specific convention that times past 24:00 belong to the previous service day, which is non-obvious behavior. However, it does not explicitly state the operation is read-only, nor does it mention error conditions or permission requirements.

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 two sentences with zero waste. The core purpose is front-loaded, followed by the essential GTFS caveat. Every sentence earns its place.

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?

The output schema exists, so return values need not be explained. However, for a two-parameter query with 0% schema coverage and no annotations, the description leaves gaps around parameter formats/defaults and read-only behavior. It is adequate but not complete for an agent to invoke correctly without inference.

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

Parameters2/5

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

Schema coverage is 0%, so the description must compensate. It implies the parameters 'route' and 'date' from context, but does not specify the date format, what a null date means (e.g., today?), or what form the route identifier takes. This leaves important parameter semantics undocumented.

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 states a specific purpose: whether a route runs on a date and its first/last trip times, including holiday exceptions. It is clear what resource and operation are involved, though it does not explicitly differentiate itself from siblings such as service_status or scheduled_departures.

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?

There is no explicit when-to-use guidance, no mention of alternatives, and no prerequisites. The description implies the tool is for checking service calendars, but offers no routing advice to help an agent choose it over siblings.

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

service_statusService StatusA

The first call for any trip or "what's going on" question: subway closures as station spans with windows, active_now and shuttle info; suspended or detoured surface routes (with skipped-stop counts and reasons); reduced speed zones; elevators and escalators out by station; counts and as_of. Answers are best effort, from the TTC's own site endpoints.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

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

With no annotations, the description carries the full burden and does meaningful work: it discloses the data provenance ("the TTC's own site endpoints"), an accuracy caveat ("best effort"), and a freshness field (as_of) that lets the agent judge staleness. It does not mention auth, rate limits, or caching, but for a zero-parameter read tool this is solid disclosure.

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 usage guidance is front-loaded in the first clause, then the payload contents are listed in a scannable semicolon series with zero filler. It is a single long sentence and slightly run-on, which costs it a point, but every clause names a distinct returned artifact.

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?

An output schema exists, so the description need not explain return shapes, yet it still sketches the major sections so the agent knows what to expect. Combined with the best-effort caveat and freshness field, an agent has enough to call it and interpret results, with only minor gaps around data recency guarantees.

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 tool takes zero parameters, so there is nothing for the description to disambiguate and the baseline of 4 applies. The schema is empty with additionalProperties false, consistent with the description implying a global, unfiltered snapshot.

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 enumerates concrete resources returned (subway closures as station spans, suspended/detoured surface routes with skipped-stop counts, reduced speed zones, elevator/escalator outages, counts and as_of), so an agent knows exactly what this returns. It positions itself as the entry point ahead of siblings like alerts, detours and service_changes, though it never names those siblings explicitly.

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

Usage Guidelines4/5

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

"The first call for any trip or 'what's going on' question" gives an explicit trigger condition and tells the agent to prefer this over the narrower siblings. It stops short of stating when-not-to-use or naming which sibling to fall back on for a specific sub-question.

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

stationStationB

A subway or LRT station by id, name or alias: rail platforms (with open/closed status), surface stops at the station (bus bays, streetcar platforms, with status), entrances, accessibility summary from the rail platforms, service per line (normal / reduced / suspended with the closure), and every disruption touching the station.

NameRequiredDescriptionDefault
queryYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior3/5

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

No annotations are supplied, so the description carries the full burden. It discloses useful behavioral content — open/closed status, service level per line with the closure, and all disruptions touching the station — but says nothing about how `query` is matched (exact id vs fuzzy name/alias), whether multiple matches can occur, or what happens on a miss.

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?

A single sentence, front-loaded with the resource and accepted keys before the parenthetical detail. It is dense and parenthesis-heavy but every clause carries content; nothing is filler.

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?

An output schema exists, so the long enumeration of returned fields is largely redundant, while genuinely missing information (station-vs-stop disambiguation, query matching semantics, not-found behavior) goes unaddressed. Complete enough to attempt a call, but not complete enough to call confidently.

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

Parameters3/5

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

Schema coverage is 0% — `query` is a bare string with no description — so the description must compensate. 'By id, name or alias' does add real meaning over the schema, but it gives no id format, no example, and no indication of whether ambiguity is tolerated, leaving the most-used parameter still underspecified.

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 names the resource (subway or LRT station), the accepted lookup keys (id, name or alias), and enumerates the payload (rail platforms, surface stops, entrances, accessibility, service per line, disruptions). That is a specific and distinguishable purpose, but it never contrasts itself with the sibling `stop` tool, which sounds near-identical to an agent scanning the tool list.

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?

Usage is only implied: the phrase 'by id, name or alias' signals a point lookup when you already have an identifier. There is no statement of when to prefer `find_stops` or `nearest_stops` for discovery, or `stop` for single surface stops, and no exclusions or prerequisites.

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

stopStopB

One stop or platform by public stop number: name, coordinates, wheelchair flag, routes/directions/headsigns served, station membership, and the disruptions touching it (its station's closures, detours skipping it, alerts on its routes).

NameRequiredDescriptionDefault
numberYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.4/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. Enumerating the returned facets implicitly conveys that this is a pure read/lookup with no side effects, which is the key behavioral signal here. It does not state behavior on an invalid or unknown stop number, nor mention rate limits or data freshness for the live disruption data, so the transparency is adequate but not thorough.

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?

One dense sentence that front-loads the scope ('One stop... by public stop number') then lists the payload facets. Nothing is wasted, though the missing leading verb makes it read as a spec fragment rather than an instruction.

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?

An output schema exists, so restating the returned fields is largely redundant effort; the space would have been better spent on the input parameter and on error behavior. As a single-parameter lookup it is close to complete, but the unexplained input format and unstated not-found behavior leave real gaps.

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

Parameters3/5

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

Schema description coverage is 0% on the single required parameter, so the description must compensate. It does partially: 'public stop number' distinguishes a rider-facing stop ID from an internal identifier, which the bare schema type 'string' does not convey. It still gives no format, example, or validation hint (e.g., numeric vs. alphanumeric).

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 names the specific resource and scope: exactly one stop, looked up by public stop number, with the returned facets enumerated (name, coordinates, wheelchair flag, routes/headsigns, station membership, disruptions). The singular 'one stop' implicitly contrasts with the plural discovery siblings find_stops and nearest_stops, but it never names them explicitly and omits a leading verb, so differentiation must be inferred.

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?

'by public stop number' implies this is the lookup for agents that already hold a stop identifier, and the sibling set (find_stops for searching, nearest_stops for geolocation) makes the alternative paths guessable. However, there is no explicit when-to-use statement, no mention of what to do if the stop number is unknown, and no exclusions.

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

vehiclesVehiclesA

Live positions of the buses and streetcars on a route, or one vehicle by fleet number: coordinates, bearing, speed, current stop (public number), headsign and crowding. Rail lines (1, 2, 4, 5, 6) publish no positions and answer unavailable; use next_trains. Answers are best effort, from the TTC's BusTime GTFS-Realtime feed; historical occupancy is out of scope.

NameRequiredDescriptionDefault
routeNo
vehicle_idNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

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

With no annotations, the description carries the full burden and does well: it names the upstream source (BusTime GTFS-Realtime), flags results as best effort, and declares historical occupancy out of scope. It does not state what happens when neither parameter is supplied, which matters for a tool with zero required parameters.

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?

Three sentences, front-loaded with the capability, then the exclusion, then the caveat. The field list is dense but every element (bearing, speed, crowding) is a concrete selection signal for the agent.

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?

An output schema exists, so return shape need not be explained, yet the description still summarizes the key fields and adds the data-quality caveat. The only missing piece is the semantics of calling with no arguments, which the zero-required-parameter schema leaves live.

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?

Schema description coverage is 0%, so the prose must supply meaning, and it does: `route` selects all vehicles on a route while `vehicle_id` is a fleet number for a single vehicle. It stops short of giving route format (number vs. name) or precedence when both are supplied, which is a real gap at 0% coverage.

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?

Opens with a specific verb+resource ("Live positions of the buses and streetcars") and enumerates the returned fields, so an agent knows exactly what it gets. It also carves itself off from the sibling `next_trains` by naming the rail lines it cannot serve.

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

Usage Guidelines5/5

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

Gives an explicit when-not (rail lines 1, 2, 4, 5, 6 publish no positions), the resulting behavior ("answer unavailable"), and the concrete alternative (`next_trains`). This is the strongest possible form for a retrieval tool.

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.

  1. 19 tool updatesv0.1.0
    • First observedaccessibility
    • First observedalerts
    • First observedarrivals
    • First observedconnections
    • First observeddetours
    • First observedfeed_status
    • First observedfind_stops
    • First observednearest_stops
    • First observednext_trains
    • First observedroute_shape
    • First observedroute_stops
    • First observedroutes
    • First observedscheduled_departures
    • First observedservice_changes
    • First observedservice_hours
    • First observedservice_status
    • First observedstation
    • First observedstop
    • First observedvehicles

TDQS

A3.6/5.0

Scored across 19 tools

Disambiguation3/5

Most tools have distinct roles, but several clusters overlap: alerts, service_status, detours and service_changes all surface disruption data; arrivals and next_trains both return next trains at platforms; and stop, station, accessibility and nearest_stops/find_stops all resolve locations. The detailed descriptions do provide routing hints (e.g. 'Schedule only; use arrivals for real time', 'service_status is the first call'), which mitigates but does not eliminate misselection risk.

Naming Consistency4/5

All names use snake_case, giving a consistent surface form. Most are noun-only (stop, station, routes, alerts, accessibility), with a minority using verb_noun (find_stops, nearest_stops), which is a minor stylistic deviation but still readable and predictable.

Tool Count4/5

19 tools is at the heavy end but each maps to a distinct slice of a genuinely large transit domain (search, real-time, schedules, disruptions, accessibility, trip planning, geometry, feed health). It is slightly over the comfortable range, but no tool appears clearly redundant.

Completeness5/5

The surface covers the full rider lifecycle: discovery (find/nearest/stop/station), routing (routes/route_stops/route_shape), scheduling (service_hours/scheduled_departures), real-time (arrivals/next_trains/vehicles), disruptions (alerts/service_status/detours/service_changes), accessibility, and trip planning (connections), plus feed health. No obvious operational gaps remain for a TTC transit server.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A Model Context Protocol server for Toronto Transit Commission (TTC) that exposes subway, streetcar, and bus schedules, real-time arrivals, vehicle positions, and service alerts, plus multi-modal trip planning.
    12
    1
    9 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables natural-language queries about live Belgian trains, metro, tram and bus services — departures, arrivals, A-to-B journey planning, disruptions and train composition — from Claude or any MCP client speaking Streamable HTTP. It runs remotely on Cloudflare Workers, needs no accounts, and stores no personal data.
    MIT