All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog 1.1.0, and this project adheres to Semantic Versioning.
- Rebranded che-transport-mcp → Rush. Binary
Rush, plugin/marketplacerush; self-contained product repo (MCP source + plugin shell + marketplace catalog). Repo renamed PsychQuant/che-transport-mcp → PsychQuant/rush (GitHub redirect covers old URLs). - No change to the 27-tool surface, routing behavior, or 3-tier cache; in-plugin MCP server key
transportand keychain serviceche-transport-tdxpreserved (existing TDX credentials keep working). - Migration: the plugin name changed, so existing installs reinstall under
rush(no silent auto-upgrade across the name change).
- Security:
minutesOfDay(TimetableRouter+MetroTools) now bounds the HH/mm components before the multiply, preventing an integer-overflow trap (crash / DoS) on malformed input —rail_route's caller-supplieddepart_afterwas the exposed path (e.g. a multi-digit hour overflowedh * 60and Swift traps on overflow). The bound also validates the time. Flagged by automated security review of v0.6.0.
rail_route(from, to, depart_after?, system)— TRA time-dependent O/D routing (#7, Stage 1 of the time-dependent transit-routing engine). Routes over the realDailyTrainTimetable(per-train departure/arrival times) with a connection-scan earliest-arrival search, then applies liveTrainLiveBoarddelays so the chosen itinerary reflects current conditions — a delayed train can lose to a later on-time one. Returnslegs[](train_no, from/to, dep/arr times, delay_min,source: live|scheduled) +arrival_time+duration_min. TRA-only (the only mode with both a public timetable and a live delay board); distinct fromrail_find_trains(which lists all trains). Tool count 22 → 23. Reuses existingRailODFare/RailStopTime/RailLiveTrainmodels + existing registry endpoints (both v3-wrapped, decoded viaTDXDecode.list); graceful when the timetable or live board is unavailable (empty + note ≠ error).
metro_find_routenow does cross-line transfer routing (#6), not just direct. The metro network is modelled as a graph (stations as nodes; same-line adjacency fromS2STravelTime; transfer edges from a newLineTransferdataset weighted by walk time + estimated boarding wait) and the shortest path is returned. A direct route is simply a zero-transfer path, so the same query also catches cases where a 環狀線 transfer beats a long direct ride.- New
metroLineTransferregistry endpoint +MetroLineTransfermodel + contract case (contract cases 30 → 31). Single-line systems (HTTP 400 / empty) degrade gracefully to no transfer edges.
metro_find_routeoutput shape evolved from the v0.4.0 flat single-line shape toroutes[], each withlegs[](one leg per line ridden — line name/colour, per-leg travel time, headway),transfers[](one per line change — interchange station, from/to line,walk_min, estimatedwait_min),transfer_count, and totaltravel_time_min. Tool count unchanged (22 — extends the existing tool). The #5 direct short-circuit gate is replaced by always building the graph.
metro_find_route(from, to, system)— direct (single-line) metro O/D routing across the 6 metro systems (TRTC/TYMC/KRTC/TMRT/NTDLRT/KLRT). Returns the connecting line (name + colour), station-to-station travel time, and current-period service headway — metros run on headways, not fixed timetables, so it does not return a specific departure time. No direct line → emptyroutes+ transfer hint (transfer routing tracked in #6). Tool count: 21 → 22. (#5)- 4 metro routing endpoints (
StationOfRoute/S2STravelTime/Frequency/Line) added to theTDXEndpointsregistry + live contract enumeration (contract cases 26 → 30). Production resolves all paths through the registry; no inline metro path literals.
- Travel-time accumulation is direction-agnostic: TDX
S2STravelTimestores segments in a single direction only (e.g. 板南線 stores the descending order), and adjacent-station run-time is symmetric, so both orders are matched. Headway is selected by Asia/Taipei weekday + time-of-day band (national-holiday detection is out of scope for v1).
TDXEndpoints— single source of truth for every TDX API path; production tools resolve paths through it (no inline path literals).- Registry-driven live contract tests (
ContractTests) — one assertion per non-static endpoint (not-404 → 200 → decode), opt-in viaTDX_CONTRACT, skipped without credentials. Newcontract-tests.ymlruns them nightly / on release / on dispatch (never on PRs). TDXDecode.list— tolerates both bare-array and wrapped-object ({…,"<Dataset>":[…]}) TDX responses.
- Rail endpoint path drift (#4): THSR is
v2notv3; THSR timetable isDailyTimetable; metro station isv2/Rail/Metro/Station/{op}; traffic news isLive/News/Freeway. - Wrapped-response silent-empty bugs in traffic (×3) and parking (×2) — production decoded
[]because TDX wraps the array. FlightInfo.DepartureRemark/ArrivalRemarkdecoded asString(wasLocalizedName) —air_find_flightswas returning empty.rail_status_trainuses the v3TrainLiveBoardcollection +$filter(the/Train/{no}path-param form 404s).
- Maritime tools (
maritime_list_routes,maritime_status_schedule). TDX serves no maritime endpoint on its unified API (everyv2/v3Maritime/Shippath 404s) and the legacy PTXShipAPI is decommissioned (403 regardless of auth). Tool count: 23 → 21, modes: 7 → 6. See #4.
- Tool JSON output no longer leaks IEEE-754 float noise. Coordinates like
25.04previously rendered as25.039999999999999becauseJSONSerializationformats everyDoublewith up to 17 significant digits — and rounding the value cannot fix this (25.04has no exact IEEE-754 representation, so the rounded result is the same bit pattern). NewJSONSanitize.cleanrecursively rewrites everyDoubleto its shortest round-trippable form viaNSDecimalNumber(Double.description), wired into all 8 tool-output serialization sites (6jsonResulthelpers + 2 inlineRailToolssites). Value-preserving (exact numeric round-trip),Int/Booluntouched, non-finite values and raw TDX passthroughs unaffected. (#1, PR #2)
0.2.2 — 2026-05-23
--setupnow delegates toche-keychainif installed in~/bin/,/usr/local/bin/,/opt/homebrew/bin/, or$PATH. Soft dependency — whenche-keychainis available, the user sees a native macOS dialog (NSAlert + NSStackView with NSTextField + NSSecureTextField) for both fields in one popup, no Terminal getpass prompt. The dialog runs inside the signedche-keychainbinary so the typedclient_secretnever enters this process either. Whenche-keychainis not found, the existing in-process getpass flow runs unchanged — no behavior regression.- Setup banner under the getpass fallback now points users at the
che-keychaininstall URL for the nicer UX. - Updated TDX portal navigation hint:
會員中心 → 資料服務 → API 金鑰 → 編輯(the previous wording skipped the資料服務submenu and the編輯reveal step, which is exactly where new users get stuck).
0.2.1 — 2026-05-22
CheTransportMCP --setup— interactive TDX credential setup built into the binary. Prompts forclient_id(visible) andclient_secret(hidden, viagetpass), writes both to keychain viaAuth.save, then verifies with a real OAuth round-trip. Replaces the repo-coupledscripts/setup-tdx.shas the canonical setup path: single signed+notarized artifact, shared keychain code withAuth.read(no read/write drift), unit-testable validation.Setup.swift+ 6SetupTests.
--helptext +AuthError.itemNotFoundmessage now point atCheTransportMCP --setupinstead ofmake setup-tdx(the latter only works inside a cloned source repo; the binary is what plugin users actually have)
0.2.0 — 2026-05-21
23 tools across all 7 transport modes (Rail / Bus / Bike / Air / Maritime / Traffic / Parking). Architecture refactored around a ToolRegistry actor so mode modules can coexist. Released to PsychQuant/che-transport-mcp.
- New
ToolRegistryactor aggregates[Tool]+ per-name dispatchers across mode modules. MCP swift-sdk only allows onewithMethodHandler(ListTools.self)/withMethodHandler(CallTool.self)per Server — the registry lets each moderegister(into:)append without overwriting. Server.swift now installs the two MCP handlers exactly once.
bus_search_routes/bus_search_stops— fuzzy match with 臺/台 normalizationbus_find_routes— O/D intersection via/v2/Bus/StopOfRoute/City/{City}bus_status_arrivals— ETA at stop via EstimatedTimeOfArrival +$filterbus_status_positions— live positions via RealTimeNearStop- City required (not optional): 22 parallel fan-out would exceed TDX 50/min, and "中山路" exists in many cities — disambiguation needed
bike_search_stations— name search + optionalservice_typefilterbike_stations_nearby— haversine distance sort + live availability join, radius clamped to [50, 3000] mbike_status_station— single-station live rent/return count
air_list_airports— Taiwan airport masterair_find_flights— schedule lookup by airport + Arrival/Departure (10-min cache)air_status_flights— live FIDS board (no cache)- IATA 3-letter validation with case-insensitive uppercase normalization
maritime_list_routes— route master, optional operator_id filtermaritime_status_schedule— raw TDX JSON pass-through wrapped in{route_id, raw}envelope (per-operator schema varies)
traffic_freeway_live— section-level speed / travel time / congestion (no cache)traffic_incidents— 5-min cached news feed with client-side keyword filtertraffic_cctv— 24h cached CCTV inventory with stream URLs
parking_list_lots— off-street car park master with keyword filterparking_status— live available-spaces lookup with optional lot_id filter
rail_search_stations在未指定system時改用withThrowingTaskGroup平行抓取 8 個 system 的 station 列表,cold cache 首次呼叫延遲大幅下降;TaskGroup yield order 非確定,故額外按RailSystem.allCases重排以保持輸出穩定(backlog A3)Cacheactor 引入預設 1000 筆的 LRU 上限(maxEntries可注入),含 keyOrder bookkeeping 與 TTL 過期同步清理,避免長時 session 記憶體無界成長;行為向後相容(既有 3 個 cache test 不需改動)(backlog A4)RailTools.register改 signature 從(server:, client:, cache:)改為(into:, client:, cache:)— 接 ToolRegistry 而非直接 install MCP handlers。Server.swift統一 install 一次
TDXError.rateLimited錯誤訊息誤導:原本說「retry in 60s」但實際只 retry 一次(1s sleep)。改為描述真實行為與 TDX per-minute window(backlog A1)rail_status_station的window_min在 schema 接受但 TDX endpoint 自帶預設視窗、client 並未過濾 — 在CLAUDE.md工具清單下加 forward-compatibility 註記(backlog A2)
- Total test count rose from 18 → 52 (+34): ToolRegistry (3), Bus (7), Bike (7), Air (4), Maritime (2), Traffic (3), Parking (4), Cache LRU (4)
- All 50 unit tests pass; 2 integration tests still XCTSkip without TDX credentials
0.1.0 — 2026-05-20
First public-ready cut. Infrastructure + 5 Rail tools shipped.
- Swift Package Manager project skeleton with MCP swift-sdk 0.12+ dependency
Cache.swift— actor-based in-memory TTL cache (24h / 1h / 0s tiers)Auth.swift— macOS Keychain-backed credential storage under serviceche-transport-tdxTDXClient.swift— TDX OAuth2 client credentials flow, in-memory token cache (60s early refresh), HTTP fetch with bearer auth, 429 single-retry with 1s sleep, 401 token invalidation, percent-encoded form bodies, guarded URL constructionServer.swift— MCP stdio server with unifiedListTools/CallTooldispatchmain.swift— CLI entrypoint with--version/--help/--check-authflagsscripts/setup-tdx.sh— interactive credential bootstrap viasecurityCLIMakefile—build/test/setup-tdx/check-auth/cleantargets
rail_list_systems()— list 8 supported rail systems (TRA / THSR / TRTC / TYMC / KRTC / TMRT / NTDLRT / KLRT)rail_search_stations(query, system?)— fuzzy station name search with 臺/台 bidirectional normalization, returns matches across all systems by defaultrail_find_trains(from, to, date, system)— O/D timetable lookup with strict YYYY-MM-DD validation (round-trip check +en_US_POSIXlocale), TRA/THSR onlyrail_status_train(train_no, system)— live train delay/position viaTrainLiveBoard/Train, TRA/THSR onlyrail_status_station(station_id, system, window_min?)— live station board viaStationLiveBoard/Station, TRA/THSR only
RailModels.swift— Codable structs for TDX schema (LocalizedName, RailPosition, RailStation, RailTrainInfo, RailStopTime, RailODFare, RailLiveTrain) plusRailSystemenum withdisplayNameandapiPathproperties
- Unit tests: AuthTests (2), CacheTests (3), TDXClientTests (2), RailModelsTests (2), RailToolsTests (5), SmokeTest (1) — 15 total, all passing
- Integration tests:
RailIntegrationTests(3 tests, 2 of whichXCTSkipgracefully when no TDX credentials in keychain) - JSON fixtures in
Tests/CheTransportMCPTests/Fixtures/(oauth_response, rail_station, rail_timetable)
CLAUDE.md— agent interaction discipline with NSQL confirmation protocol reference, ambiguity hotspots table (中山/下一班/方向/車種)README.md+README_zh-TW.md— bilingual project entry, tool catalog, roadmapdocs/superpowers/specs/2026-05-20-che-transport-mcp-design.md— full design spec (architecture, tool catalog, cache/error/testing strategy)docs/superpowers/plans/2026-05-20-plan-1-infrastructure-and-rail.md— 18-task implementation plan (executed via subagent-driven-development)
- Smart wrapper over thin pass-through — tools combine multiple TDX endpoints when needed, normalize fields, provide concept-level operations
- Unified MCP dispatch — single
withMethodHandler(ListTools.self)+ singlewithMethodHandler(CallTool.self)with switch-by-name (matches MCP swift-sdk 0.12 actual API; the plan's per-toolregisterToolpseudocode was adjusted at T10) - 3-tier cache TTL — 24h static (stations/routes), 1h timetable, 0s live
- Empty ≠ error — empty result sets return normally; only system-level failures (auth, network, rate limit, schema drift) return
isError: true - 臺/台 bidirectional normalization — both query and station name pass through the same normalization before comparison
TDXError.rateLimitederror message says "retry in 60s" but actual single-retry sleeps 1s only (cosmetic mismatch)rail_status_stationacceptswindow_minparameter but TDX endpoint uses its own default window — accepted-but-ignored, not documented in CLAUDE.mdrail_search_stationswith nosystemfilter fires 8 sequential HTTP requests on cold cache (24h cache means steady-state cost is negligible, but cold start can take seconds)Cacheis unbounded — fine for rail (~500 KB), needs size cap before bus stops land in v0.2