stl-transit
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| STL_HOME | No | Directory to store snapshots and data for the stl-transit tool. If not set, defaults to ~/.local/share/stl-transit. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| stl_doctorA | Health of the local environment: store location, snapshot count, disk use, which configured sources are usable and which are blocked on an unresolved URL. Call this first if you do not know what data is available locally. |
| stl_snapshot_sourcesA | List every configured feed and page: agency, region, URL, whether it is usable, how many snapshots exist locally, and when the latest was fetched. |
| stl_snapshot_listA | List stored snapshots newest-first, with pins. Args: kind: 'gtfs' or 'rt'. Omit for all. source: source name, e.g. 'metro_gtfs'. Omit for all. |
| stl_snapshot_fetchA | Download a feed from the network into the local snapshot store. Uses conditional requests, so an unchanged feed returns unchanged=true and costs a 304 rather than re-downloading. The GTFS zip is ~3.5 MB and expands to ~29 MB, so this can take a few seconds. Args: source: 'metro_gtfs', 'metro_rt_trips', 'metro_rt_vehicles', 'metro_rt_alerts'. force: bypass the conditional-request cache. |
| stl_gtfs_coverageA | Service date range and days remaining before the feed expires. Metro publishes a feed whose service data ends at the next quarterly pick. Check this before trusting any departure result: an empty departures list is frequently an expired feed rather than an absent bus. |
| stl_gtfs_filesB | Inventory of files in the GTFS zip with row counts and columns, plus a list of optional GTFS files that are ABSENT and why each absence matters (transfers, fares, frequencies, pathways...). |
| stl_gtfs_statsA | Headline counts: agencies, routes broken down by route type, stops, trips, stop_times rows, shape points, service_ids. |
| stl_gtfs_featuresB | Which GTFS features this feed provides, phrased to line up with the badges on the Mobility Database feed page (route colors, shapes, headsigns, wheelchair accessibility, fares, pathways, transfers, frequencies). |
| stl_gtfs_schemaB | Columns, null rates, distinct counts and sample values for one GTFS file. Args: table: GTFS file name with or without .txt, e.g. 'stops' or 'stop_times.txt'. |
| stl_gtfs_queryA | Run one read-only SQL query against the imported GTFS feed. Tables are the GTFS filenames without .txt: agency, stops, routes, trips, stop_times, calendar, calendar_dates, shapes, feed_info. All columns are TEXT, including numeric-looking ids -- leading zeros are meaningful in GTFS. Writes, ATTACH and PRAGMA are denied at the database driver; a wall-clock timeout and row/byte caps are enforced. Only a single statement is accepted. This is the general-purpose escape hatch: anything the named tools do not cover can be expressed here. Args: sql: a single SELECT or WITH statement. limit: max rows (hard cap 1000). |
| stl_gtfs_routesB | List routes with ids, short and long names, type and trip counts. Args: route_type: GTFS route_type as a string. '3' is bus, '0' tram/streetcar, '1' subway, '2' rail. search: case-insensitive substring match across all route fields. |
| stl_gtfs_routeB | One route in detail: directions, headsigns, per-service trip counts, and the first and last departure time in each direction. Args: route_id: the GTFS route_id, NOT the number on the front of the bus. Metro's route_ids look like '19731B'; run stl_gtfs_routes with a search term to map a rider-facing number onto one. |
| stl_gtfs_stopsA | Search stops by name substring, rider-facing stop code, or serving route. Args: search: substring of stop_name, case-insensitive. code: exact stop_code match. route_id: return every stop served by this route. |
| stl_gtfs_stopA | One stop resolved by rider-facing code or internal id, with both identifiers, parent station, accessibility flags, coordinates, and the routes serving it. Args: stop: a stop_code (the number on the sign) or a stop_id. |
| stl_gtfs_stop_resolveA | Determine which GTFS field holds the number printed on a bus stop sign. Reports coverage, uniqueness and observed format for both stop_code and stop_id, checks Metro's own published example (15111), and returns a verdict. This matters more than it looks: the Light SDK exposes no usable location API, so 'stops near me' is not buildable and the app's entire input UX is stop-number entry. Run this once and rely on the verdict. |
| stl_gtfs_departuresA | Scheduled departures at a stop for a time window. Schedule only, no realtime. Correctly attributes departures encoded past 24:00:00 to the previous service date, and resolves service_ids through both calendar.txt and calendar_dates.txt. Each result carries its service_date and raw gtfs_time alongside the resolved local time, so a wrong service-date attribution is visible rather than hidden. Args: stop: stop_code (number on the sign) or stop_id. at: ISO-8601 instant. Naive values are read as America/Chicago local time. Defaults to now. window_minutes: how far ahead to look. route: optional route_id or route_short_name filter. |
| stl_gtfs_calendarB | service_ids active on a date, showing the calendar.txt weekly pattern and each calendar_dates.txt exception SEPARATELY rather than pre-merged, so you can see whether a date's behaviour came from the weekly pattern or from an exception. Args: on: ISO date (YYYY-MM-DD). Defaults to today. |
| stl_gtfs_service_dayA | Which GTFS service date(s) a wall-clock instant could belong to, with the corresponding gtfs_time for each. Use this whenever a departure time looks off by a day. GTFS measures times from noon-minus-twelve-hours, not local midnight -- on DST transition days those differ by an hour. Args: timestamp: ISO-8601 instant. Naive values read as America/Chicago. |
| stl_gtfs_late_nightB | Trips whose stop times cross the service-day boundary, plus the maximum departure_time anywhere in the feed. Use it to find edge-case test material. Args: threshold: GTFS time string; departures at or after it are returned. |
| stl_rt_healthA | Staleness and entity counts for the locally stored realtime feeds, and whether the three feeds agree on their header timestamp. Args: entity: 'trip_updates', 'vehicle_positions', or 'alerts'. Omit for all. |
| stl_rt_decodeB | Decode a stored GTFS-Realtime snapshot into normalized JSON. Fields present in the bytes but absent from the schema map are preserved under '_unknown' rather than dropped, because silently discarding fields is how you ship a decoder that is wrong in ways nobody notices. Args: entity: 'trip_updates', 'vehicle_positions', or 'alerts'. |
| stl_rt_wireA | Raw protobuf wire-format dump of a realtime snapshot: field number, wire type, length, bytes and nesting, with the named path for each numeric path. This is the ground-truth artifact for validating a hand-written decoder. Point the Kotlin implementation at the same snapshot and compare trees. Args: depth: how deep to recurse into submessages. max_entities: how many feed entities to dump (keeps output bounded). |
| stl_rt_schema_censusA | Which protobuf fields this feed actually populates, and at what rate, across N stored snapshots. Decides what to model in Kotlin: low-rate fields can be skipped in v1, and any path reported as unmodelled is present in the bytes but missing from the schema map, which needs investigating before porting. Args: samples: how many recent snapshots to census. |
| stl_rt_referenceA | The full GTFS-Realtime field map as a flat table (message, field number, name, kind, repeated) plus all enum value mappings. This is the porting reference for the on-device decoder. The Light SDK dependency allow-list contains no protobuf runtime, so the Kotlin decoder is either kotlinx-serialization-protobuf or hand-written from this table. |
| stl_rt_stop_arrivalsA | Scheduled departures with realtime predictions merged in -- exactly what the app should render. When no realtime snapshot is available it degrades to scheduled-only and says so explicitly, which is the behaviour the app must also have. Args: stop: stop_code or stop_id. at: ISO-8601 instant, America/Chicago if naive. Defaults to now. |
| stl_oracle_casesB | The golden-fixture case list for the Kotlin test gate, each with the specific failure mode it pins down (DST transitions, 24:xx rollover, holiday service mapping, expired feed, realtime absent, and so on). |
| stl_oracle_generateA | Compute expected departure outputs and write committed fixture JSON files. Output is byte-stable for a given snapshot (sorted keys, fixed indent) so that a later verify run is a meaningful drift check. Args: spec_path: JSON file binding each case id to concrete inputs, e.g. {"weekday_midday": {"stop": "15111", "at": "2026-08-05T12:00:00"}}. out_dir: directory to write fixtures into. case: generate only this case id. |
| stl_oracle_verifyA | Recompute every committed fixture against the current feed and report which ones no longer match. Drift means either the feed changed or the fixtures are stale -- both are things you want to learn from a scheduled run, not a user. A case that legitimately raises (unknown stop code, expired feed) is a first-class expectation, compared on error type rather than message, so it does not read as permanent drift. Args: fixtures_dir: directory of committed fixture JSON, normally the tool repo's test resources rather than anywhere in this store. |
| stl_support_explain_emptyA | Diagnose why a stop shows no departures, by walking the decision tree and naming the branch: unknown stop code, expired feed, no service that date, stop present but never served, or simply too narrow a window. Use this whenever stl_gtfs_departures returns an empty list, instead of guessing at the cause. Args: stop: stop_code or stop_id. at: ISO-8601 instant, America/Chicago if naive. |
| stl_assert_listA | The assumptions this app makes about the feed that Metro never promised. Each one names what it checks and, more usefully, what breaks in the app if it stops holding. Read this before adding a feature that depends on feed behaviour, so the dependency gets encoded rather than discovered later by a user. |
| stl_assert_runA | Evaluate the assumption suite against the current feed. Every result carries the OBSERVED value beside the threshold, so a failure is actionable without a second call: "stop_code coverage 0.982, threshold 0.99" tells you how bad it is, "FAIL" does not. Three outcomes, not two. Args: only: assumption ids to run. Omit for all. baseline: snapshot id or pin name for the stability assumptions (stop_ids_stable, rail_route_ids_stable). Without it those skip. |
| stl_assert_explainA | One assumption in full: why it matters, which code path depends on it, and how to remediate a failure. Call this on anything stl_assert_run reports as failing, before deciding what to do about it. Args: assumption_id: an id from stl_assert_list, e.g. 'stop_code_unique' or 'rt_join_rate'. |
| stl_diff_summaryA | Everything that changed between two GTFS snapshots, in one screen. Findings are graded, because a pick that renames three headsigns is routine and one that retires four hundred stop codes is not, and an ungraded list of deltas makes the reader do that triage themselves. Args: a: the earlier snapshot id or pin name. b: the later one. Direction matters and is never normalized. |
| stl_diff_stop_idsA | Survival rate of stop_id and stop_code across a service change. The single most consequential number in this whole tool. The app's saved- stops feature lives or dies on it: every code that does not survive a pick is a user whose saved stop silently stops working, with no error and no way for them to tell what happened. Args: a: the earlier snapshot id or pin name. b: the later one. Run stl_snapshot_list to see what is stored. |
| stl_web_listA | Metro web pages configured for capture, with their last capture and content hash. Fares, holiday schedules, the developer terms, and the upcoming-schedule-changes page -- everything the app needs that is not in the GTFS feed. |
| stl_web_captureA | Fetch, normalize, extract and store a Metro web page. Hashes the EXTRACTED content, never the raw HTML: raw HTML changes on every request (analytics ids, nonces, rotating images), so hashing it would make every later drift check a false positive. Rate-limited to one fetch per page per day by default. Metro is a public agency whose infrastructure this tool is an unpaid guest on. Args: page: 'fares', 'holidays', 'purchase', 'schedule_changes', 'developer_terms', 'rider_alerts'. Omit to capture all. force: bypass the interval gate and the conditional-request cache. |
| stl_web_extractA | Structured data pulled out of a stored page capture. fares -> fare rows with prices in integer cents; holidays -> holiday rows with BUS and RAIL service kept separate; schedule_changes -> the pick id; others -> normalized text. Args: page: the page key. Run stl_web_list for valid values. |
| stl_web_checkA | Has any watched page changed since its last capture? This is the surveillance job. A changed fares page means the app's bundled fare table is now lying to riders; a changed developer-terms page means the redistribution rights this whole project rests on may have moved. |
| stl_bundle_faresA | The fare table the app ships, with its as_of date and source URL baked in. Fares are NOT in the GTFS feed -- this reads the latest capture of Metro's fares page, so run stl_web_capture first if it reports nothing. Prices are integer cents; a fare table carrying 2.4999999 is a bug that reaches riders. Args: fmt: 'json', or 'kotlin' to emit compilable Kotlin source. Hand-copying a fare table into Kotlin is how a stale fare reaches a rider. |
| stl_bundle_holidaysA | Holiday to service-type mapping, bus and rail kept distinct. On a holiday MetroBus runs SUNDAY service while MetroLink runs WEEKEND service. Those are different concepts that happen to coincide most of the time, and merging them produces a wrong answer on exactly the days a rider is most likely to check. Args: year: the calendar year to resolve holiday dates against. Defaults to the current year, which is usually but not always what you want near a year boundary. |
| stl_bundle_size_reportB | On-device size budget: bytes per table, with index cost isolated. The raw feed is ~29 MB expanded and the LP3 is a minimalist device, so this is what decides which pruning strategy the shipped app uses. Args:
compact_path: a database built by |
| stl_report_briefA | The state of the feed right now, in one call, with the next command to run. Composes coverage, the assumption suite, realtime health and web drift. Every input is optional and absences are reported rather than silently passed, so this still works on a machine that has only ever fetched the static feed. Start here if you do not know what is wrong. |
| stl_report_handoffA | Verified facts about the feed as a markdown block, with citations. Written for pasting into a CLAUDE.md handoff document. Every claim carries the snapshot id and date it was verified against, so a later reader can re-verify rather than trust -- the feed moves, and an uncited fact in a handoff doc silently becomes a lie. Includes the sharp edges that bite a Kotlin port: 24:xx encoding, DST arithmetic, stop_code vs stop_id, the absent protobuf runtime, fares living off-feed. |
| stl_support_reproA | Reconstruct exactly what the app should have shown at a stop and instant. This is how "stop 15111 showed nothing at 11:47 last Tuesday" gets answered without a device and without waiting for Tuesday. When the answer is empty it also returns WHY it is empty, rather than leaving you to guess. Args: stop: stop_code (the number on the sign) or stop_id. at: ISO-8601 instant; naive values read as America/Chicago. rt_snapshot: a stored realtime snapshot id to merge in, for reproducing a complaint about a wrong prediction. |
| stl_support_diff_deviceA | Diff what a device actually rendered against what it should have. Deliberately forgiving about the shape of Args: expected_json: a file path or inline JSON -- typically the output of stl_support_repro. actual_json: a file path or inline JSON captured from the device. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 45 tools
Each tool has a clearly distinct purpose: static GTFS inspection, realtime decoding, oracle fixtures, web capture, diffing, and support reconstruction are cleanly separated even where they touch similar data. Potentially adjacent tools like stl_gtfs_departures, stl_rt_stop_arrivals, and stl_support_repro are explicitly differentiated by schedule-only vs realtime-merged vs device-reproduction intent.
Names follow a highly consistent stl_<domain>_<topic> snake_case pattern, with domain prefixes like gtfs, rt, oracle, web, diff, assert, bundle, report, and support. Plural/singular distinctions and verb choices are uniform and predictable across the set.
45 tools is far beyond the 3-15 well-scoped range and well above the 25+ 'too many' threshold. The breadth is justified by the domain's complexity, but the raw count still makes the surface heavy and harder for an agent to scan efficiently.
The tool surface covers the full lifecycle: fetching static and realtime feeds, inspecting GTFS schema and data, querying departures and calendars, decoding realtime protobuf, generating/verifying oracle fixtures, diffing snapshots, capturing web pages, bundling fares/holidays, running assumptions, and producing reports and support reconstructions. No obvious dead-end workflow remains.