Skip to main content
Glama

gbfs-mcp

An MCP server for GBFS bikeshare feeds. It works with any of the ~1,500 systems that publish GBFS — Citi Bike, Capital Bikeshare, Vélib', Ecobici — and it's guaranteed to work with BIXI Montréal, because BIXI is the default and the one I actually test against.

The thing I wanted was simple. I'm at work, I want to bike home, and I want to ask Claude whether that's going to work before I walk outside:

me    → "I'm at work, can I bike home?"

Claude→ check_route(origin, destination)

      → Yes. 138 bikes across 15 stations near you.
        At the other end Regina / de Verdun is empty right now,
        but there are 35 bikes and 35 free docks within a 9 minute walk.
        Feed last reported 4 minutes ago.

That's the whole point. Everything below is in service of that answer being trustworthy.

What it does and what it doesn't

It tells you what a feed says right now. That's it. No history, no predictions, and no saved addresses — you pass coordinates in. I kept it that way on purpose: it stays general enough to work in any city, and nothing personal ends up on a public endpoint.

Two rules run through all of it.

Unknown is not zero. If a feed doesn't report dock counts, that means it doesn't know. If I returned 0 there you'd ride to a station believing it's full when it might be half empty. So unknowns say they're unknown. Ask for cargo bikes on a feed that can't count them and you get unknown, not 0.

Nearby beats exact. These feeds lag. BIXI's median station last reported about 7 minutes ago, and the slowest 10% are closer to half an hour. So "this station has 2 docks" is a much weaker answer than "there are 30 docks within 400m". get_neighbourhood and check_route pool everything nearby for that reason, and every station tells you how old its reading is.

Related MCP server: mcp-citybikes

Tools

Nine of them. Full reference with real output in docs/tools.md.

tool

what it answers

find_stations

Where are the stations near here, or named this? Sorted by distance, with walk times.

get_station

Everything about one station.

get_neighbourhood

How many bikes and docks within a radius. The answer that survives a stale feed.

check_route

Can I ride A to B right now? Grades both ends good / tight / poor / unknown.

get_network_status

Whole system at a glance — totals, empty and full stations, how fresh the data is.

get_alerts

Service alerts, filtered to the ones actually in effect.

get_system_info

Operator details — timezone, contact, website.

list_vehicle_types

What the vehicle type ids mean for this system.

describe_system

What version this feed is, how I worked that out, and what it can and can't tell you.

Every tool takes an optional system. Before you conclude a system "has no e-bikes", call describe_system — plenty of older feeds simply cannot report them, and that's a different thing from having none.

Running it

npm install
npm test           # 69 tests against saved real feeds, no network needed
npm run test:live  # hits the actual feeds

Locally, over stdio

{
  "mcpServers": {
    "gbfs": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/gbfs-mcp/src/stdio.ts"]
    }
  }
}

Deployed, as a Cloudflare Worker

npx wrangler login
npm run deploy

It's stateless JSON-RPC over HTTP. No Durable Objects, no KV, no database. The server is read-only and never pushes anything to the client, so it doesn't need sessions or SSE — which is why it scales to zero and costs nothing sitting idle. GET / gives you a description, MCP clients POST to the same URL.

Adding another city

Four systems are built in: bixi, citibike, capitalbikeshare, ecobici. Add more with the GBFS_SYSTEMS env var, no code change. Discovery URLs are in MobilityData's catalog:

GBFS_SYSTEMS = '''[
  {"id":"velib","name":"Vélib' Métropole",
   "discoveryUrl":"https://velib-metropole-opendata.smovengo.cloud/opendata/Velib_Metropole/gbfs.json"}
]'''

There's a pinVersion field too, for when a system's newer feed is broken and you want to stay on the old one.

The version problem

This is the part that took the most work, so it gets its own doc: docs/gbfs.md.

Short version: GBFS runs from 1.0 to 3.0 and the versions genuinely break each other. Of the systems in the catalog, 964 are on 2.3, 562 on 3.0, 239 on 2.2 (that's BIXI), 167 on 1.1, and 114 on 1.0. You can't just pick one and ignore the rest.

So the client negotiates. If a system publishes gbfs_versions.json it takes the newest version it understands and follows that URL. Otherwise it reads the version field. If there's no version field at all, the feed predates 1.1, so it's 1.0.

This isn't box-ticking. Citi Bike defaults to 1.1, and 1.1 has no vehicle_types at all. Its 2.3 tree does. Negotiating up is the difference between being able to answer "is there an e-bike here" and not, across 2,509 stations.

Feed quirks I ran into

Every one of these came from pointing the code at a live feed, not from reading the spec. Details in docs/gbfs.md.

  • BIXI's root gbfs.json only advertises the old 1.0 tree. The 2-2 tree it actually registers isn't linked from it.

  • BIXI publishes no gbfs_versions feed — 404 everywhere I looked for it.

  • BIXI has a station with a negative last_reported. Do the arithmetic naively and it looks 56 years old.

  • BIXI's alerts feed still serves entries from 2023 and 2024. Show those unfiltered and you'd tell someone the network is closed for a snowstorm three years ago.

  • Citi Bike writes its versions as "v2.3", not "2.3". The spec says bare X.Y. I rejected the prefix at first and it silently stuck the client on 1.1.

  • v3.0 feed URLs drop the .json extension, so I match feeds by name instead of building paths.

  • Station ids are opaque strings. "345" at BIXI, a UUID at Citi Bike, "ICONIC" at Ecobici. Don't assume they're numbers.

Layout

More detail in docs/architecture.md.

src/core/     pure logic, imports nothing from MCP or Workers
src/mcp/      tool definitions, shared by both transports
src/worker.ts Cloudflare entrypoint
src/stdio.ts  local entrypoint
test/fixtures real feeds I captured — v1.1, v2.2 and v3.0

src/core/ doesn't know MCP exists. If I ever want this logic behind a plain HTTP API instead, it lifts out unchanged.

  • bixi-monitor — long-run history for one BIXI station, plus the dashboard

  • A second MCP for station 345 specifically — its history and prediction models — is planned separately. This one stays general and holds nothing personal.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MackTr/gbfs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server