@cyanheads/transitland-mcp-server
Provides geocoding capabilities to resolve locations into coordinates for use with geography-filtered transit tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/transitland-mcp-serverfind operators near 37.7749,-122.4194"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Overview
Transit data from the Transitland v2 registry — the open aggregator of GTFS, GTFS-Realtime, and GBFS feeds from thousands of transit operators worldwide. Find operators, discover feeds and their license terms, and look up routes, stops, and real-time-aware departures from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
Tools
Tool | Description |
| Find transit operators/agencies by name, point/radius, bounding box, country/region, or Onestop ID |
| Fetch the full operator record by Onestop ID — agencies, feeds, and source tags |
| Discover GTFS, GTFS-Realtime, and GBFS feeds — fetch URLs, license terms, and freshness |
| Find routes by point/radius, bounding box, operator, Onestop ID, or GTFS mode |
| Find stops/stations by point/radius, bounding box, Onestop ID, or operator network |
| Departures from a stop, each flagged |
Transitland does not geocode place names — resolve a location to coordinates with a geocoding MCP server (e.g. openstreetmap-mcp-server's openstreetmap_geocode) before calling a geography-filtered tool.
Resources
Resource | Description |
| Operator record by Onestop ID — agencies, places served, published feeds, and source tags |
| Feed record by Onestop ID — spec, fetch URL, license terms, and freshness |
All resource data is also reachable via tools (transitland_get_operator, transitland_find_feeds).
Related MCP server: mcp-transit-land
Capability reference
transitland_find_operators tool
Filter by
search(name),lat+lon+radius(max 100,000m, default 1,000m),bbox,onestop_id, oradm0_name/adm1_name(country/region) — at least one required, or the call fails asno_filterReturns each operator's Onestop ID, name, places served, published feeds (a
GTFS_RTentry signals real-time departures may be available), and Wikidata QIDlatrequireslonand vice versa, or the call fails asincomplete_pointlimitcaps at 100 (default 20), paginated via theaftercursor
transitland_get_operator tool
Accepts an Onestop ID (e.g.
o-9q9-bart) or an internal integer IDReturns agencies (each with places served), published feeds, and source tags: Wikidata QID, US NTD ID, general Twitter/X handle
Idempotent single-record lookup; mirrored by the
transitland://operator/{onestop_id}resourceoperator_not_foundwhen the ID doesn't resolve
transitland_find_feeds tool
Filter by
operator_onestop_id(fromtransitland_find_operators— the reliable path to one agency's feeds),spec(gtfs/gtfs-rt/gbfs/mds),search, orfetch_error— at least one requiredEach feed returns its fetch URL, real-time endpoints when present, and license terms — redistribution, commercial use, derived products, and attribution as explicit
yes/no/unknown(never inferred from a blank field), plus SPDX identifier and attribution text where knownFreshness: last-fetch timestamp, content hash, and the calendar window the current data covers
authorizationRequiredflags feeds whose download needs a separate key/registrationlimitcaps at 100 (default 20), paginated viaafter
transitland_find_routes tool
Filter by
lat+lon+radius(max 50,000m, default 1,000m),bbox,operator_onestop_id,onestop_id,route_type(GTFS mode integer), orsearch— at least one requiredReturns short/long name,
route_typemapped to a human-readable mode (bus, subway, rail, ferry, tram, …), brand color, operating agency's Onestop ID, and source feed's Onestop IDScheduled (GTFS static) route definitions, not live vehicle positions
latrequireslonand vice versa, or the call fails asincomplete_pointlimitcaps at 100 (default 20), paginated viaafter
transitland_find_stops tool
Filter by
lat+lon+radius(max 10,000m, default 500m),bbox,onestop_id, orserved_by_onestop_ids(comma-separated operator/route Onestop IDs) — at least one requiredReturns coordinates,
location_typewith a label (stop, station, entrance, node, boarding area), wheelchair accessibility, timezone, and the parent station's Onestop ID for child platformsDepartures attach to platform-level stops (
location_type0) — a station may return none; use its child platformslimitcaps at 100 (default 20), paginated viaafter
transitland_get_departures tool
Resolve a stop to its Onestop ID with
transitland_find_stopsfirstEvery departure carries a
realtimeflag —truefor a live GTFS-Realtime prediction,falsefor a static scheduled time — plus ascheduleRelationship(STATIC,SCHEDULED,ADDED,CANCELED,UNSCHEDULED,DUPLICATED)Returns scheduled and (when real-time) estimated times with delay in seconds, route, headsign, mode, trip, direction, and accessibility
Top-level
realtimeAvailablereports whether the stop's feed publishes GTFS-RT at allnext_secondslook-ahead window: 60–86,400 (default 3,600); widen it or setuse_service_window: truewhen a stop returns nothingstop_not_foundwhen the stop doesn't resolve — detected from an empty upstream array, not an HTTP 404
transitland://operator/{onestop_id} resource
Mirrors
transitland_get_operator— agencies, places served, published feeds, and source tags (Wikidata QID, US NTD ID, Twitter/X handle)onestop_idparam accepts an Onestop ID (e.g.o-9q9-bart) or internal integer IDoperator_not_foundwhen the ID doesn't resolve
transitland://feed/{onestop_id} resource
Mirrors a single-feed result from
transitland_find_feeds— spec, fetch URL, real-time endpoints, license terms, and latest-fetch freshnessonestop_idparam accepts a feed Onestop ID (e.g.f-9q9-bart) or internal integer IDLicense fields normalize blank registry values to
unknown/null — never inferred as permissivefeed_not_foundwhen the ID doesn't resolve
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Transitland-specific:
Direct HTTP client for the Transitland v2 REST API — no SDK dependency, a flat query-param contract over a handful of GET endpoints
Onestop IDs (
o-/f-/r-/s-) are the identifier spine — surfaced in every result, accepted on input alongside internal integer IDsFeed-version history sliced to the latest version (the raw endpoint returns the entire fetch log — 167 entries for BART), so a feed lookup returns current freshness, not a multi-year history
Coverage polygons and route/stop geometry omitted by default — a compact country/region/city place summary instead
Agent-friendly output:
The real-time distinction is structural — a per-departure
realtimeboolean andscheduleRelationship, plus a top-levelrealtimeAvailable, so an agent branches on data, never on parsing a timestampLicense terms surfaced honestly — blank registry fields normalize to
unknown/null and are never inferred as permissive; the schema descriptions tell agents to confirm against the license URL before redistributingThe pagination
meta.nextURL embeds the API key in plaintext; the service discards it and surfaces only the opaque integeraftercursor, so the key never reaches tool output or logsEmpty results return data plus an actionable notice (geocode-first, widen the window, try a child platform) rather than an error
Getting started
Add the following to your MCP client configuration file. A Transitland API key is required — see Prerequisites for how to get one.
{
"mcpServers": {
"transitland-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/transitland-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"TRANSITLAND_API_KEY": "your-api-key"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"transitland-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/transitland-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"TRANSITLAND_API_KEY": "your-api-key"
}
}
}
}Or with Docker:
{
"mcpServers": {
"transitland-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "TRANSITLAND_API_KEY=your-api-key",
"ghcr.io/cyanheads/transitland-mcp-server:latest"
]
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 TRANSITLAND_API_KEY=your-api-key bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
A Transitland v2 API key — register at the Transitland developer portal. The free tier is rate-limited; the Pro tier raises the quota.
Installation
Clone the repository:
git clone https://github.com/cyanheads/transitland-mcp-server.gitNavigate into the directory:
cd transitland-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set TRANSITLAND_API_KEYConfiguration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. A missing TRANSITLAND_API_KEY fails at startup with a banner naming the variable, not at the first tool call.
Variable | Description | Default |
| Required. Transitland v2 API key, sent as the | — |
| Transitland REST API base URL. Override to pin a specific deployment or a self-hosted instance. |
|
| Transport: |
|
| Port for the HTTP server. |
|
| HTTP endpoint path where the MCP server is mounted. |
|
| Authentication mode: |
|
| Log level ( |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry instrumentation (spans, metrics, completion logs). |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t transitland-mcp-server .
docker run --rm -e TRANSITLAND_API_KEY=your-key -p 3010:3010 transitland-mcp-serverThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/transitland-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| Resource definitions ( |
| Transitland v2 REST client — HTTP, |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools and resources via the barrels in
src/mcp-server/*/definitions/index.tsWrap the Transitland API: validate raw → normalize to domain type → return output schema; never fabricate missing fields (especially license terms — blanks are
unknown, not permissive)
Data & licensing
Transit data is provided by Transitland — an open registry aggregating GTFS, GTFS-Realtime, and GBFS feeds from thousands of operators worldwide. Products built on Transitland data must display the name "Transitland" with a link to transit.land/terms, clearly visible to end users.
Transitland aggregates feeds from thousands of operators, each of which may carry its own license and attribution requirements. Review the per-feed license terms at transit.land/terms and comply with each source feed's requirements before redistributing or publishing data obtained through this server.
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Transitland MCP — global GTFS aggregator
MobilityData's open catalogue of public-transport feeds — ~4,500 GTFS schedule feeds and ~1,900…
Read-only public transit departures, stop search, and city coverage for bus and train users.
MBTA MCP — Boston real-time transit via the MBTA v3 API (api-v3.mbta.com)
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server that provides tools for querying live transit data (stops, departures, routes, vehicles, alerts) from any WP GTFS Pro site, enabling AI assistants to answer rider questions.3 npmGPL 2.0
- AlicenseNot gradedqualityBmaintenanceEnables querying global transit data including agencies, routes, stops, and departures through a GTFS aggregator.165 npmMIT
- FlicenseNot gradedqualityBmaintenanceMCP server exposing live MBTA V3 transit data, with tools for routes, stops, arrivals, alerts, and vehicle positions.-
- AlicenseNot gradedqualityBmaintenanceEnables searching for buses and trains, and when direct routes are unavailable, it provides the underlying data needed to assemble multi-leg itineraries with transfers.4 npmISC