From 9ada5a77dfaff7c3956788cd71daca17b68963df Mon Sep 17 00:00:00 2001 From: CabLate Date: Fri, 3 Jul 2026 17:10:50 +0800 Subject: [PATCH 1/2] fix: include transitDetails in Routes API field mask (#78) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COMPUTE_ROUTES_FIELD_MASK was missing routes.legs.steps.transitDetails, so transit steps only returned a free-text navigationInstruction ("Bus towards X") without structured metadata (bus number, stop names, headsign, agency). Google Routes API only returns this object when explicitly requested via X-Goog-FieldMask. Adds the field to the mask — additive only, no impact on driving/walking responses (the field is absent in non-transit steps anyway). Adds a smoke test using Dublin Airport -> Trinity College Dublin, which virtually always returns a bus step. The test asserts that at least one step contains transitDetails with transitLine/stopDetails; if Google returns a walking-only itinerary, the test warns and skips rather than flaky-failing. --- src/services/RoutesService.ts | 1 + tests/smoke.test.ts | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/services/RoutesService.ts b/src/services/RoutesService.ts index 6065e19..6f6721d 100644 --- a/src/services/RoutesService.ts +++ b/src/services/RoutesService.ts @@ -25,6 +25,7 @@ const COMPUTE_ROUTES_FIELD_MASK = [ "routes.legs.steps.staticDuration", "routes.legs.steps.startLocation", "routes.legs.steps.endLocation", + "routes.legs.steps.transitDetails", "routes.legs.polyline", "routes.optimizedIntermediateWaypointIndex", ].join(","); diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index 5df0bc5..45ece4d 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -1103,6 +1103,53 @@ async function testTransitErrorMessages(session: McpSession): Promise { } } +async function testTransitDetailsField(session: McpSession): Promise { + if (!API_KEY) { + console.log(" ā­ļø Skipped transitDetails test (no GOOGLE_MAPS_API_KEY)"); + return; + } + + console.log("\nšŸ” Testing transit step includes transitDetails (field mask)..."); + + // Dublin Airport → city centre: virtually always returns a bus step (issue #78 scope). + const result = await sendRequest(session, "tools/call", { + name: "maps_directions", + arguments: { + origin: "Dublin Airport, Ireland", + destination: "Trinity College Dublin, Ireland", + mode: "transit", + }, + }); + const content = result?.result?.content ?? []; + assert(content.length > 0, "Transit directions in Dublin returns content"); + if (content.length === 0) return; + + const text = content[0]?.text ?? ""; + const isError = result?.result?.isError === true; + assert(!isError, `Transit directions in Dublin should succeed, got error: ${text.slice(0, 200)}`); + if (isError) return; + + const parsed = JSON.parse(text); + const steps: any[] = parsed?.routes?.[0]?.legs?.flatMap((l: any) => l.steps ?? []) ?? []; + assert(steps.length > 0, "Transit response has at least one step"); + + const transitStep = steps.find((s: any) => s.transitDetails); + if (!transitStep) { + // Routes API may rarely return a walking-only itinerary; warn but don't flaky-fail. + console.log( + " āš ļø No transit step in this itinerary — field mask correctness cannot be asserted this run" + ); + return; + } + + const td = transitStep.transitDetails; + assert( + td?.transitLine || td?.stopDetails, + "transitDetails contains structured transit metadata (transitLine or stopDetails)", + `got: ${JSON.stringify(td).slice(0, 200)}` + ); +} + // --------------- Main --------------- async function main() { @@ -1127,6 +1174,7 @@ async function main() { await testToolCalls(session); await testPlaceDetailsPhotos(session); await testTransitErrorMessages(session); + await testTransitDetailsField(session); await testMultiSession(); } catch (err) { console.error("\nšŸ’„ Fatal error:", err); From c6affeea6564a2cd820f5e401333e3d0629cef8d Mon Sep 17 00:00:00 2001 From: CabLate Date: Fri, 3 Jul 2026 22:12:19 +0800 Subject: [PATCH 2/2] style: apply prettier to route tool schemas and smoke test CI runs format:check on every PR; this is the first PR to trigger it since the workflow was added. The avoid_tolls one-liner in three route tools (introduced in #80) and a long console.log line in the new transit test both exceeded prettier's print-width and are split to match the existing avoid_highways formatting. No semantic changes. --- src/tools/maps/directions.ts | 5 ++++- src/tools/maps/distanceMatrix.ts | 5 ++++- src/tools/maps/planRoute.ts | 5 ++++- tests/smoke.test.ts | 4 +--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/tools/maps/directions.ts b/src/tools/maps/directions.ts index 0550b57..9934f28 100644 --- a/src/tools/maps/directions.ts +++ b/src/tools/maps/directions.ts @@ -15,7 +15,10 @@ const SCHEMA = { .describe("Travel mode for directions"), departure_time: z.string().optional().describe("Departure time (ISO string format)"), arrival_time: z.string().optional().describe("Arrival time (ISO string format)"), - avoid_tolls: z.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), + avoid_tolls: z + .boolean() + .optional() + .describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), avoid_highways: z .boolean() .optional() diff --git a/src/tools/maps/distanceMatrix.ts b/src/tools/maps/distanceMatrix.ts index 7ce97a1..7819674 100644 --- a/src/tools/maps/distanceMatrix.ts +++ b/src/tools/maps/distanceMatrix.ts @@ -19,7 +19,10 @@ const SCHEMA = { .describe( "Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware duration estimates." ), - avoid_tolls: z.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), + avoid_tolls: z + .boolean() + .optional() + .describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), avoid_highways: z .boolean() .optional() diff --git a/src/tools/maps/planRoute.ts b/src/tools/maps/planRoute.ts index 7f8d106..10a2a92 100644 --- a/src/tools/maps/planRoute.ts +++ b/src/tools/maps/planRoute.ts @@ -19,7 +19,10 @@ const SCHEMA = { .string() .optional() .describe("Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware routing."), - avoid_tolls: z.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), + avoid_tolls: z + .boolean() + .optional() + .describe('Avoid toll roads where reasonable. Only supported with mode "driving".'), avoid_highways: z .boolean() .optional() diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index 45ece4d..ce32e5a 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -1136,9 +1136,7 @@ async function testTransitDetailsField(session: McpSession): Promise { const transitStep = steps.find((s: any) => s.transitDetails); if (!transitStep) { // Routes API may rarely return a walking-only itinerary; warn but don't flaky-fail. - console.log( - " āš ļø No transit step in this itinerary — field mask correctness cannot be asserted this run" - ); + console.log(" āš ļø No transit step in this itinerary — field mask correctness cannot be asserted this run"); return; }