chelaile-mcp
车来了 MCP
一个基于 MCP 的车来了实时公交服务器,让大语言模型可以查询国内的公交、地铁实时数据 —— 包括线路时刻表、车辆实时位置、附近站点、关键词搜索、以及公交+地铁的换乘路线规划。
回答 "我的公交还有多久到站" 这类问题。无需登录、无需任何账号配置,开箱即用。
安装
一键安装
点击上面的徽章即可在 Cursor / VS Code / VS Code Insiders 中一键安装。
在 Claude Code 中使用
claude mcp add chelaile -- npx -y chelaile-mcp-server或者手动编辑 ~/.claude/mcp_servers.json(或项目级的 .mcp.json):
{
"mcpServers": {
"chelaile": {
"command": "npx",
"args": ["-y", "chelaile-mcp-server"]
}
}
}在 Claude Desktop 中使用
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"chelaile": {
"command": "npx",
"args": ["-y", "chelaile-mcp-server"]
}
}
}重启对应客户端后,工具会以 chelaile 为前缀出现。
Related MCP server: openbusdata
工具一览
工具 | 用途 |
| 列出支持的城市及其 ID |
| 某城市的刷新间隔限制与展示策略 |
| 基于调用方 IP 估算位置(精度到城市级) |
| WGS-84 经纬度 → 中文地址 |
| 按关键词混合搜索:线路 + 站点 + POI |
| 按某一分类分页"查看更多" |
| 某 GPS 坐标附近的站点及到站预计时间 |
| 某站点经过的所有线路 + 附近的地铁 |
| 某线路的完整站点列表 + 当前车辆 |
| 某线路的地图轨迹坐标 |
| 某线路即将到达某站点的车辆实时信息 |
| 某线路上所有车辆的位置与载客率 |
| 首末班、发车间隔或完整时刻表 |
| 一次性批量刷新多个 (线路, 站点) 对 |
| 两个 GCJ-02 坐标之间的公交+地铁换乘规划 |
每个工具都支持 response_format: "markdown" | "json"(默认 markdown),返回 JSON 时同时附带 structuredContent。
License
MIT
Available Tools
15 toolsbus_get_city_configGet city operating configARead-onlyIdempotent
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" } }
Inputs: city_id (required), response_format.
bus_get_line_detailGet full line detail (stops + live buses)ARead-onlyIdempotent
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
Inputs: city_id (required), line_id (required), lat, lng, +1 more.
bus_get_line_realtimeGet realtime buses for a (line, stop) pairARead-onlyIdempotent
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
Inputs: city_id (required), line_id (required), target_order (required), station_id (required), +3 more.
bus_get_line_routeGet line polyline coordinatesARead-onlyIdempotent
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.
Inputs: city_id (required), line_id (required), include_shape, response_format.
bus_get_my_locationGet the caller's approximate location via IPARead-onlyIdempotent
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:
Call this tool to identify the user's city (match 'city' field against bus_list_cities to get a cityId).
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.
Inputs: ip, response_format.
bus_get_nearby_stopsGet nearby bus stopsARead-onlyIdempotent
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")
Inputs: city_id (required), lat (required), lng (required), limit, +1 more.
bus_get_stop_detailGet stop detail with all linesARead-onlyIdempotent
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.
Inputs: city_id (required), physical_st_id (required), namesake_st_id, first_line_id, +3 more.
bus_get_timetableGet a line's per-trip schedule (rarely useful)ARead-onlyIdempotent
Per-trip departure schedule for a line. The upstream returns one of three modes via mode:
'scheduled' (timeTableType=1): an explicit
timetablearray 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 — ..." }
Inputs: city_id (required), line_id (required), line_no (required), direction (required), +1 more.
bus_list_citiesList supported citiesARead-onlyIdempotent
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.
Inputs: hot_only, response_format.
bus_list_line_busesGet the nearest approaching bus on a lineARead-onlyIdempotent
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"} } ] }
Inputs: city_id (required), line_id (required), target_order (required), station_name (required), +1 more.
bus_plan_transitPlan a public-transit routeARead-onlyIdempotent
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.
Inputs: city_id (required), origin_name (required), origin_lat (required), origin_lng (required), +5 more.
bus_refresh_linesBatch refresh realtime info for multiple (line, stop) pairsARead-onlyIdempotent
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.
Inputs: city_id (required), line_stn (required), response_format.
bus_reverse_geocodeReverse geocode GPS to addressARead-onlyIdempotent
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.
Inputs: lat (required), lng (required), response_format.
bus_searchSearch lines, stations, and POIsARead-onlyIdempotent
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
Inputs: city_id (required), keyword (required), response_format.
bus_search_moreSearch more results of one categoryARead-onlyIdempotent
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.
Inputs: city_id (required), keyword (required), response_format, type.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 15 tools
Each tool has a clearly distinct purpose. Overlapping areas like realtime arrivals are split into bus_get_line_realtime (full roster with ETA) and bus_list_line_buses (imminent arrivals), with descriptions explicitly differentiating them. No ambiguous overlap.
All tools follow a consistent 'bus_verb_noun' pattern with snake_case. Minor inconsistency: 'get' vs 'list' prefixes (e.g., bus_get_nearby_stops vs bus_list_cities) but both are clear and predictable.
15 tools is well-scoped for a bus transit data server, covering search, line info, realtime, routing, geocoding, and configuration. Each earns its place without bloat.
The tool surface covers the full lifecycle: search (bus_search), line details (bus_get_line_detail), realtime arrivals (bus_get_line_realtime), nearby stops (bus_get_nearby_stops), stop details (bus_get_stop_detail), route planning (bus_plan_transit), and geocoding (bus_reverse_geocode). No obvious gaps for the stated purpose.
Maintenance
Related MCP Connectors
MCP server for China Railway 12306 ticket availability: schedules and seats by Chinese station name.
MCP server for China Railway 12306 ticket availability: schedules and seats by Chinese station name.
MCP server for China administrative divisions: province/city/district lookup, adcode resolution and
MCP server for IPv4 geolocation lookup with Chinese output, supports batch queries.
Related MCP Servers
- AlicenseAqualityCmaintenanceProvides access to Deutsche Bahn train timetables, station information, and schedule changes through Model Context Protocol tools and resources.4236 npm17MIT
- AlicenseCqualityCmaintenanceMCP server for the UK Bus Open Data Service, enabling timetable queries, stop search, route discovery, journey planning, and real-time bus tracking.1622 PyPIMIT
- FlicenseAqualityDmaintenanceMCP server for querying German public transport information, including station search, live departures/arrivals, and journey planning.51-
- FlicenseNot gradedqualityCmaintenanceAn MCP server that provides real-time Hong Kong public transport ETA information.-