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.
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" }
}
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.
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.
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
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.
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")
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.
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
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.
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
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"} }
]
}
+3 more tools listed on main page