# PeanutSplash/chelaile-mcp [Health: Active]

**Category:** 🚆 Travel & Transportation  
**Repository:** https://github.com/PeanutSplash/chelaile-mcp  
**GitHub Stars:** 17  
**npm Downloads (last month):** 175  
**Views:** 5  
**Installs:** 1  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/peanutsplash-chelaile-mcp

## Description
A Chelaile-powered MCP server that lets LLMs query realtime bus and metro data in China — arrival times, vehicle positions, timetables, nearby stops, and transit routing. No login required.

## Tools
Capabilities this server exposes over MCP:

- **bus_list_cities** — List cities supported by the realtime bus data service.

Args:
  - hot_only (boolean, default true): return only the upstream's curated 'hot' set (~20 cities). Set false to dump the full ~480-city list (token-heavy, use sparingly).
  - response_format ('markdown' | 'json'): defaults to 'markdown'

Returns (json):
  {
    "cities": [
      { "cityId": "034", "cityName": "上海", "pinyin": "ShangHai", "supportSubway": true, "hot": true },
      ...
    ]
  }

Use when: the user mentions a city name and you don't have its ID. The hot set covers the top-tier cities the user almost certainly means.
- **bus_get_city_config** — Get a city's runtime config: max poll interval and "arriving" time threshold.

This is mostly relevant if you are deciding how aggressively to refresh — not for end-user questions about lines or stops.

Args:
  - city_id (string, required): e.g. '034' (Shanghai), '027' (Beijing)
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "maxInterval": 30,
    "arrivingStationLimitSeconds": 180,
    "busDisplayConfig": { "lineDetail": "time#order#distance", "other": "time#order" }
  }
- **bus_reverse_geocode** — Convert WGS-84 lat/lng to a Chinese postal address (province, city, district, township, formatted address).

Useful when you have raw GPS coordinates and need a human-readable place name, or the citycode/adcode to pass to other tools.

Args:
  - lat (string, required): WGS-84 latitude, decimal, e.g. '31.230416'
  - lng (string, required): WGS-84 longitude, decimal, e.g. '121.473701'
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "formatted": "上海市黄浦区...",
    "province": "上海市",
    "city": "上海市",
    "district": "黄浦区",
    "township": "南京东路街道",
    "citycode": "021",
    "adcode": "310101"
  }

For municipalities (Shanghai/Beijing/Tianjin/Chongqing) the upstream emits an empty 'city' value; this tool back-fills it with 'province' so the field is always a usable string.
- **bus_get_my_location** — Resolve the caller's approximate location from their public IP — useful when the user asks something like "what's near me" without providing coordinates.

**Precision is city-level (typically a few kilometres).** Good enough to identify the city and seed bus_get_nearby_stops with a starting guess. NOT precise enough to find the user's actual bus stop — for that, ask for a landmark/address and resolve it via bus_search.

Caveats:
  - Resolves via ip-api.com (free tier; rate-limited but no auth).
  - VPN / corporate proxy → result reflects the proxy exit IP, not the user.
  - Cellular IPs often land on a provincial centroid.

Args:
  - ip (string, optional): a specific IPv4/IPv6 to look up. Omit to use the mcp server process's own outbound IP (= the caller's machine when running locally).
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "lat": 31.2222,
    "lng": 121.4581,
    "gpsType": "wgs",
    "city": "上海",
    "region": "上海市",
    "country": "中国",
    "ip": "116.236.0.1",
    "isp": "China Telecom",
    "precision": "city-level (~10 km); not suitable for stop-level queries"
  }

**Suggested workflow**:
  1. Call this tool to identify the user's city (match 'city' field against bus_list_cities to get a cityId).
  2. Pass lat/lng into bus_get_nearby_stops for a rough nearby list, OR ask the user to confirm a landmark and use bus_search.pois for sharper coordinates.
- **bus_search** — Search inside a city by keyword. Returns matching lines, stations, and POIs in one call.

Use this as the primary entry point when the user gives a line number, station name, or destination name without IDs.

**Keyword tip**: plain "71", "71路", "地铁2号线", "陆家嘴" all work — the upstream is reasonably forgiving. If a short numeric returns empty, try appending "路".

**Coordinate systems**:
  - 'pois' coords are GCJ-02 (use directly with bus_plan_transit)
  - 'stations' coords are WGS-84 (use with bus_get_nearby_stops / bus_get_line_realtime)
  Both are also marked with a 'gpsType' field.

Args:
  - city_id (string, required): e.g. '034'
  - keyword (string, required)
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "highlightKey": "71路",
    "lines": [
      {
        "name": "71", "lineNo": "r95817", "isSubway": false,
        "directions": [
          { "direction": 0, "lineId": "21283603183", "startSn": "延安东路外滩", "endSn": "申昆路枢纽站" },
          { "direction": 1, "lineId": "21283603182", "startSn": "申昆路枢纽站", "endSn": "延安东路外滩" }
        ],
        // Compat top-level fields mirror directions[0] (or first available).
        "lineId": "21283603183", "direction": 0, "startSn": "延安东路外滩", "endSn": "申昆路枢纽站"
      },
      ...
    ],
    "stations": [{ "sId":"...", "sn":"西藏中路", "lat":31.231006, "lng":121.474316, "gpsType":"wgs", "physicalStId":"...", "namesakeStId":"...", "isSubway":false }, ...],
    "pois": [{ "name":"71路", "address":"...", "tag":"公交线路", "district":"黄浦区", "lat":31.233021, "lng":121.49073, "gpsType":"gcj" }, ...]
  }

**Line folding**: each entry in 'lines' is one logical line (e.g. "71路"). The two travel directions live in 'directions[]'. Pick the lineId matching your desired direction.

**Subway hint**: when 'isSubway' is true, the entry carries a 'hint' field — bus_get_line_detail will return empty for these lineIds. Use bus_get_stop_detail (metros field) or bus_plan_transit instead.

**Follow-ups**:
- directions[i].lineId → bus_get_line_detail (full stop list, first/last/price) — non-subway only
- stations[*].physicalStId + namesakeStId → bus_get_stop_detail
  (NOTE: a few stations lack physicalStId — typically metro-only entries with subwayV2=1. For those, use bus_get_nearby_stops to resolve the bus platform IDs nearby.)
- pois[*].lat/lng (GCJ) → bus_plan_transit as origin/destination
- **bus_search_more** — Paginated 'see more' for one category from bus_search.

Args:
  - city_id (string, required)
  - keyword (string, required): same keyword used in bus_search
  - type ('1'|'2'|'3'): 1=more lines, 2=more stations, 3=more POIs (default '1')
  - response_format ('markdown' | 'json')

Returns: same shape as bus_search but only the requested category is populated.
- **bus_get_nearby_stops** — List bus stops near a WGS-84 GPS coordinate, each annotated with the lines that pass through and the realtime buses approaching.

**If you don't have coordinates**: call bus_get_my_location first (city-level precision via IP), or ask the user for a landmark and resolve it via bus_search.pois — the resulting lat/lng goes into this tool's lat/lng args.

Args:
  - city_id (string, required): e.g. '034'
  - lat / lng (string, required): WGS-84 decimal coordinates
  - limit (number, default 5): how many of the closest stops to return (max 20)
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "stops": [
      {
        "sId": "021-15232", "sn": "西藏中路", "distance": 87, "isSubway": false,
        "physicalStId": "...", "namesakeStId": "...", "firstLineId": "...",
        "lines": [
          {
            "lineId": "...", "name": "71", "direction": 0, "endSn": "...",
            "status": "等待发车" | "不在运营时间" | "" (running),
            "preArrivalTime": "10:10" | undefined,
            "targetOrder": 2, "targetStationId": "021-15232",
            "buses": [
              { "busId": "...", "order": 2, "arrivalTime": 1779070466055, "travelTime": 25, "distanceToDest": 90, "capacity": 0 }
            ]
          }
        ],
        "subwayLines": [ { "name": "地铁2号线", "shortName": "2号线", "color": "140,194,32", "directions": [{ "destName": "...", "firstTime": "05:31", "lastTime": "23:24" }] } ]
      }
    ]
  }

Field notes:
- buses[].arrivalTime is a ms timestamp; -1 = unknown
- buses[].travelTime is seconds remaining; -1 = unknown
- buses[].capacity: 0=light, 1=moderate, 2=crowded
- If no realtime buses but the line is starting soon, 'preArrivalTime' will hold the next predicted dispatch ("10:12")
- **bus_get_stop_detail** — Full detail for a stop: precise WGS-84 coordinates, every line that passes through (with first/last/price), realtime buses, and nearby metro lines.

Args:
  - city_id (string, required)
  - physical_st_id (string, required): from bus_get_nearby_stops / bus_search
  - namesake_st_id (string, optional): recommended; from the same source
  - first_line_id (string, optional): a line to highlight
  - lat / lng (string, optional): caller's WGS-84 location, used to populate 'distance'
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "stations": [
      { "sId": "...", "sn": "...", "lat": ..., "lng": ..., "distance": ...,
        "lines": [{ "lineId": "...", "name": "71", "direction": 0, "startSn": "...", "endSn": "...", "firstTime": "05:30", "lastTime": "23:30", "price": "2元", "targetOrder": 2, "buses": [...] }],
        "metros": [{ "name": "地铁14号线", "lineNo": "14号线", "color": "97,96,32" }] }
    ]
  }

Multiple entries in stations[] mean the stop name maps to several physical platforms.
- **bus_get_line_detail** — Full info for a line: rider-facing fields (name, first/last/price, stationsNum), the full ordered station list, the reverse-direction lineId, and every bus currently on the line.

**Use this — not bus_get_timetable — to answer "is line X still running" or "first/last bus time" questions.** The timetable tool only has data for a small minority of lines.

**Subway lines are NOT supported.** If bus_search returned a line with isSubway=true (e.g. lineId=1057 for 地铁2号线), this endpoint returns an empty payload — the response will carry `empty: true` and a `hint` field pointing at bus_get_nearby_stops / bus_plan_transit. Don't retry; route to those tools instead.

Args:
  - city_id (string, required)
  - line_id (string, required): from bus_search.lines[*].lineId
  - lat / lng (string, optional): caller's WGS-84 coordinates
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "line": { "lineId":"...", "name":"71", "lineNo":"r95817", "direction":0, "startSn":"...", "endSn":"...", "firstTime":"05:30", "lastTime":"23:30", "price":"2元", "stationsNum":24 },
    "stations": [{ "order":1, "sId":"...", "sn":"...", "wgsLat":..., "wgsLng":..., "physicalStId":"...", "namesakeStId":"...", "metros":[{"name":"地铁14号线","lineNo":"14号线","color":"97,96,32"}] }, ...],
    "buses": [{ "busId":"...", "order":2, "lat":..., "lng":..., "speed":5.7, "capacity":0, "distanceToWaitStn":...}],
    "reverseDirection": { "lineId":"...", "startSn":"...", "endSn":"...", "firstTime":"04:30", "lastTime":"22:30", "price":"2元" } | null,
    "depDesc": "...", "preArrivalTime": "...", "targetOrder": 24,
    "empty": true, "hint": "..."   // present only when upstream returned no data (subway / retired line)
  }

Each station carries:
- 'order' → feed into bus_get_line_realtime / bus_list_line_buses as target_order
- 'sId' → feed into bus_get_line_realtime as station_id (NOT into bus_get_stop_detail!)
- 'physicalStId' + 'namesakeStId' → feed into bus_get_stop_detail to see every line through that stop
- **bus_get_line_route** — Polyline coordinates for drawing a line on a map. Points with 'stopOrder' are actual stops; others are shape points between stops.

Args:
  - city_id (string, required)
  - line_id (string, required)
  - include_shape (boolean, default false): false returns only stop markers (~25 points); true returns all shape points (~400-500). Skip unless you actually need to draw the line.
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "pointCount": 480,           // total shape points upstream returned
    "stopCount": 23,             // stop markers among them
    "points": [{ "lat":..., "lng":..., "stopOrder":1 }, ...]
  }
'points' is the filtered list — stops only by default, full polyline when include_shape=true.

**Known caveat**: upstream sometimes omits the terminus stop from the polyline, so 'stopCount' may be one less than bus_get_line_detail's 'stationsNum' (e.g. 23 vs 24). Trust bus_get_line_detail for the authoritative station list; line_route is just for drawing.

Markdown mode only summarises counts; request JSON to read coordinates.
- **bus_get_line_realtime** — Canonical "when will my bus arrive" tool. Returns every bus currently on the line, with the nearest one carrying an ETA to the waiting stop.

**Important**: the upstream predicts an ETA for only the nearest bus heading to your stop. Buses farther up the route are returned (with position/speed/capacity) but their `eta` field is null. That's not a bug.

**Args**:
  - city_id (string, required)
  - line_id (string, required): from bus_search
  - target_order (string, required): the waiting stop's order on the line. Source: bus_get_line_detail.stations[i].order, or bus_get_nearby_stops.stops[].lines[].targetOrder.
  - station_id (string, required): sId of the waiting stop
  - lat / lng (string, required): WGS-84 — the user's location is best; if unavailable, use the waiting stop's wgsLat/wgsLng (from line_detail.stations[i]).
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "line": { "lineId":"...", "name":"71", "direction":0, "endSn":"..." },
    "targetOrder": 2,
    "realData": true,
    "buses": [
      { "busId":"...", "licence":"...", "order":2, "lat":..., "lng":..., "speed":5.7, "capacity":0, "distanceToWaitStn":90, "eta":{"travelTime":25,"arrivalTime":1779070466055,"displayTime":"10:14"} },
      { "busId":"...", "order":3, "lat":..., "lng":..., "speed":3, "capacity":0, "eta":null },
      ...
    ],
    "note": "..."
  }

Field notes:
- The 'line' sub-object intentionally omits startSn — upstream does not return it on this endpoint. Read it from bus_get_line_detail if needed.
- eta.travelTime is seconds remaining
- eta.arrivalTime is a ms timestamp
- eta.displayTime is a "HH:MM" hint from upstream
- capacity: 0=light, 1=moderate, 2=crowded
- **bus_list_line_buses** — Returns the nearest bus heading to the anchor stop, with ETA and the bus's next stop name.

**This is narrower than the name suggests.** Despite the upstream endpoint being called "busList", in practice it returns at most 1-2 buses (the imminent ones). For the FULL roster of every bus currently on the line, call **bus_get_line_detail** — its 'buses' array lists all live vehicles with positions.

Use this tool when you want a quick "what's about to arrive" answer for a specific stop.

Args:
  - city_id (string, required)
  - line_id (string, required)
  - target_order (string, required): the waiting stop's order on the line
  - station_name (string, required): display name of that anchor stop
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "targetOrder": 2,
    "buses": [
      { "busId":"...", "licence":"...", "order":2, "lat":..., "lng":..., "speed":8.2, "capacity":0, "nextStop":"西藏中路", "eta":{"travelTime":214,"arrivalTime":..., "displayTime":"10:14"} }
    ]
  }
- **bus_get_timetable** — Per-trip departure schedule for a line. The upstream returns one of three modes via `mode`:
  - 'scheduled' (timeTableType=1): an explicit `timetable` array of trips
  - 'interval' (timeTableType=2): the line runs at a fixed headway — NO per-trip times are returned
  - 'special' / 'unknown' (timeTableType=3 or other)

**Most lines in Shanghai are 'interval'**, so this tool is rarely the right one. To answer "first/last bus", "is the line still running", or "what's the next departure", call **bus_get_line_detail** instead — it always returns firstTime / lastTime / price / live buses.

Args:
  - city_id (string, required)
  - line_id (string, required)
  - line_no (string, required): rider-facing short name from bus_search.lines[].name (e.g. '71'). Do NOT pass the internal lineNo like 'r95817'.
  - direction ('0'|'1'): the line direction
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "line": { "lineId":"...", "name":"71", "direction":0, "startSn":"...", "endSn":"..." },
    "timeTableType": 2,
    "mode": "interval",
    "timetable": null,
    "note": "This line runs at a fixed interval — ..."
  }
- **bus_refresh_lines** — Refresh realtime bus info for several (line, stop) pairs in one round-trip. Useful for a 'favourites' dashboard.

Args:
  - city_id (string, required)
  - line_stn (string, required): semicolon-separated quadruples
      Format: lineId,stopId,nextId,targetOrder;lineId,stopId,nextId,targetOrder;...
      'nextId' may be empty between the two commas.
  - response_format ('markdown' | 'json')

Example: '21283603183,021-15232,,2;21283604388,021-8685,,4'

Returns (json):
  {
    "lines": [
      {
        "line": { "lineId":"...", "name":"71", "direction":0, "endSn":"..." },
        "depDesc": "...",
        "buses": [
          { "busId":"...", "order":2, "capacity":0, "distanceToDest":881, "eta":{"travelTime":221,"arrivalTime":...} }
        ]
      }
    ]
  }

Soft cap: up to 10 quadruples per call.
- **bus_plan_transit** — Plan a public-transit (bus + metro) route between two points. Returns alternative plans sorted by recommendation, each broken into walking and ride segments.

**Coordinate system**: this tool expects GCJ-02. The easiest source is bus_search.pois[*].lat/lng — those are already GCJ-02 and carry a name. If you only have WGS-84 (e.g. from a phone GPS), convert it before calling.

Args:
  - city_id (string, required)
  - origin_name, origin_lat, origin_lng (string, required): origin in GCJ-02
  - dest_name, dest_lat, dest_lng (string, required): destination in GCJ-02
  - strategy ('0'|'1'|'2'|'3'): 0=recommended (default; surfaces metro), 1=fewest transfers, 2=least walking, 3=shortest time (BUS-ONLY — upstream often drops metro plans here, so for general "fastest route" questions use 0)
  - response_format ('markdown' | 'json')

Returns (json):
  {
    "origin": "lng,lat",
    "destination": "lng,lat",
    "distance": 17982,
    "plans": [
      {
        "duration": 3056, "walkingDistance": 1466, "distance": 19086, "tag": "直达", "transitCount": 1,
        "segments": [
          { "type": "walking", "distance": 837, "duration": 717 },
          { "type": "bus", "name": "地铁2号线", "lineType": 1, "departureStop": "人民广场", "arrivalStop": "虹桥2号航站楼", "viaStops": 8, "duration": 1800, "distance": 17620, "startTime": "05:37", "endTime": "23:30" }
        ]
      }
    ]
  }
lineType: 0=bus, 1=metro. Durations in seconds, distances in meters.

A top-level 'note' field is emitted when the response shape is suspicious — e.g. strategy=3 returned no metro plans despite this being a likely metro trip.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "chelaile-mcp": {
    "command": "npx",
    "args": ["-y","chelaile-mcp-server"]
  }
}
```

## Documentation

## What PeanutSplash/chelaile-mcp MCP server does

PeanutSplash/chelaile-mcp MCP server connects an MCP client to Chelaile transit data for cities in China. It covers both bus and metro use cases, including supported-city discovery, line and station search, nearby stop lookup, vehicle positions, arrival estimates, line schedules, and public-transit route planning.

The tool set supports several common workflows:

- Find a city ID from a Chinese city name.
- Search for a line, station, or point of interest.
- Retrieve the stops and live vehicles for a bus line.
- Check the nearest approaching bus at a selected stop.
- Identify nearby bus stops from coordinates.
- Plan a bus-and-metro journey between two places.

No login, account, or credential configuration is required according to the project documentation.

## How it works

Start with `bus_list_cities` when the city ID is unknown. `bus_search` then resolves a line number, station, or destination into internal line and stop identifiers. Those identifiers feed into detail and realtime tools such as `bus_get_line_detail`, `bus_get_stop_detail`, and `bus_get_line_realtime`.

Location handling needs attention. `bus_reverse_geocode` accepts WGS-84 coordinates and returns a Chinese address plus city and administrative codes. `bus_get_nearby_stops` also expects WGS-84. `bus_plan_transit` instead expects GCJ-02 coordinates, which can be obtained from the POIs returned by `bus_search`. IP-based location from `bus_get_my_location` is only city-level and is not precise enough to identify an actual bus stop.

Every tool accepts `response_format` as `markdown` or `json`; markdown is the default. JSON is the useful choice when an agent needs coordinates, identifiers, vehicle arrays, or structured route segments.

## Setup and configuration

Run the published npm package with `npx`:

```bash
npx -y chelaile-mcp-server
```

The README documents configurations for Claude Desktop and Cursor, and also provides a Claude Code command. For Claude Desktop, add the command and package as a server entry, then restart the client. The project’s examples use the server name `chelaile`.

PeanutSplash/chelaile-mcp MCP server has no documented environment variables or API key settings. Its IP-location feature calls the free tier of ip-api.com, which may be rate-limited. VPNs, proxies, and some cellular networks can reduce location accuracy.

## Tools and capabilities

The available capabilities include:

- List hot or complete sets of supported cities.
- Read a city’s polling and arrival-display configuration.
- Reverse-geocode WGS-84 coordinates into Chinese addresses.
- Search lines, stations, and POIs, with paginated category results.
- List nearby stops with passing lines and approaching vehicles.
- Inspect stop details, line schedules, fares, first and last service times, and nearby metro lines.
- Retrieve live buses, route polylines, and selected-stop ETAs.
- Refresh multiple line-stop pairs in one request.
- Plan alternative bus and metro routes with walking segments.

`bus_get_line_detail` is the preferred source for first and last service times and live vehicles. Subway results from `bus_search` should be handled through nearby-stop or transit-planning tools rather than the bus line-detail endpoint.

## Limitations and notes

Realtime values come from the upstream service and can be missing or unknown. In particular, `bus_get_line_realtime` normally provides an ETA only for the nearest bus heading toward the selected stop; farther vehicles may have positions but no ETA. `bus_list_line_buses` is intended for imminent arrivals and may return only one or two vehicles, while line detail provides the broader live roster.

The timetable endpoint often returns a fixed interval instead of individual departures, especially for Shanghai lines. Route geometry is for map drawing and may omit a terminal stop, so the authoritative ordered stop list comes from line detail. The full city list is large and token-heavy, making the default hot-city list preferable unless a complete list is needed.

PeanutSplash/chelaile-mcp MCP server is licensed under MIT. Its transit coverage and data freshness depend on the upstream Chelaile service, and the supplied material does not specify a guaranteed availability level.

_Full upstream README: https://allmcps.com/mcp/peanutsplash-chelaile-mcp/readme_

