diff --git a/CHANGELOG.md b/CHANGELOG.md index df808e7..d56c92e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.0.51 + +- fix: improve transit error messages for unsupported regions (Japan, India) (#74) +- fix: avoid passing default departureTime to Routes API (sporadic "Timestamp must be set to a future time" errors) + ## 0.0.50 - docs: add CODE_OF_CONDUCT.md diff --git a/src/services/PlacesSearcher.ts b/src/services/PlacesSearcher.ts index e91b4de..fb2e5d3 100644 --- a/src/services/PlacesSearcher.ts +++ b/src/services/PlacesSearcher.ts @@ -328,14 +328,14 @@ export class PlacesSearcher { arrival_time?: string ): Promise { try { - const departureTime = departure_time ? new Date(departure_time) : new Date(); + const departureTime = departure_time ? new Date(departure_time) : undefined; const arrivalTime = arrival_time ? new Date(arrival_time) : undefined; const result = await this.routesService.computeRoutes({ origin, destination, mode, - departureTime, - arrivalTime, + ...(departureTime ? { departureTime } : {}), + ...(arrivalTime ? { arrivalTime } : {}), }); return { diff --git a/src/services/RoutesService.ts b/src/services/RoutesService.ts index d3a3027..6b9d8b5 100644 --- a/src/services/RoutesService.ts +++ b/src/services/RoutesService.ts @@ -176,9 +176,15 @@ export class RoutesService { const data = await response.json(); if (!data.routes || data.routes.length === 0) { - throw new Error( - `No route found from "${params.origin}" to "${params.destination}" with mode: ${params.mode || "driving"}` - ); + const mode = params.mode || "driving"; + if (mode === "transit") { + throw new Error( + `No transit route found from "${params.origin}" to "${params.destination}". ` + + `The Google Routes API does not support transit directions in some regions (notably Japan and India). ` + + `Try using mode "driving" or "walking" instead, or use a regional transit service for public transportation details.` + ); + } + throw new Error(`No route found from "${params.origin}" to "${params.destination}" with mode: ${mode}`); } const route = data.routes[0]; @@ -218,6 +224,7 @@ export class RoutesService { durations: any[][]; origin_addresses: string[]; destination_addresses: string[]; + warning?: string; }> { const travelMode = TRAVEL_MODE_MAP[params.mode || "driving"] || "DRIVE"; @@ -260,11 +267,15 @@ export class RoutesService { const distances: any[][] = Array.from({ length: rowCount }, () => Array(colCount).fill(null)); const durations: any[][] = Array.from({ length: rowCount }, () => Array(colCount).fill(null)); + let routeNotFoundCount = 0; for (const element of elements) { const i = element.originIndex; const j = element.destinationIndex; if (i === undefined || j === undefined) continue; - if (element.condition === "ROUTE_NOT_FOUND") continue; + if (element.condition === "ROUTE_NOT_FOUND") { + routeNotFoundCount++; + continue; + } const distMeters = element.distanceMeters || 0; const durSeconds = parseDuration(element.duration); @@ -279,12 +290,28 @@ export class RoutesService { }; } + const totalPairs = rowCount * colCount; + + // All pairs failed — likely a regional transit limitation + if (routeNotFoundCount === totalPairs && travelMode === "TRANSIT") { + throw new Error( + `No transit routes found for any origin/destination pair. ` + + `The Google Routes API does not support transit directions in some regions (notably Japan and India). ` + + `Try using mode "driving" or "walking" instead, or use a regional transit service for public transportation details.` + ); + } + // Routes API doesn't return resolved addresses; use input strings return { distances, durations, origin_addresses: params.origins, destination_addresses: params.destinations, + ...(routeNotFoundCount > 0 && travelMode === "TRANSIT" + ? { + warning: `${routeNotFoundCount} of ${totalPairs} origin/destination pairs returned no transit route. The Google Routes API has limited transit coverage in some regions.`, + } + : {}), }; } } diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index a01e3b4..0158462 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -971,6 +971,96 @@ async function testExecMode(): Promise { } } +// --------------- Test 8: Transit Error Messages --------------- + +async function testTransitErrorMessages(session: McpSession): Promise { + console.log("\n🧪 Test 8: Transit error messages for unsupported regions"); + + if (!API_KEY) { + console.log(" ⏭️ Skipped (no GOOGLE_MAPS_API_KEY)"); + return; + } + + // Verify driving mode works fine first (baseline) + const driveResult = await sendRequest(session, "tools/call", { + name: "maps_directions", + arguments: { origin: "Tokyo Station", destination: "Nagoya Station", mode: "driving" }, + }); + const driveContent = driveResult?.result?.content ?? []; + assert(driveContent.length > 0, "Driving directions returns content"); + if (driveContent.length > 0) { + const text = driveContent[0]?.text ?? ""; + const isError = driveResult?.result?.isError === true; + assert(!isError, "Driving directions in Japan works (no error)", isError ? text.slice(0, 150) : undefined); + if (!isError) { + try { + const parsed = JSON.parse(text); + assert(parsed?.total_distance !== undefined, "Driving returns total_distance"); + assert(parsed?.total_duration !== undefined, "Driving returns total_duration"); + } catch { + assert(false, "Driving directions returns valid JSON"); + } + } + } + + // Test directions with transit in Japan — should return improved error message + const dirResult = await sendRequest(session, "tools/call", { + name: "maps_directions", + arguments: { origin: "Tokyo Station", destination: "Nagoya Station", mode: "transit" }, + }); + const dirContent = dirResult?.result?.content ?? []; + assert(dirContent.length > 0, "Transit directions returns content"); + if (dirContent.length > 0) { + const text = dirContent[0]?.text ?? ""; + const isError = dirResult?.result?.isError === true; + assert(isError, "Transit directions in Japan returns isError=true"); + assert( + text.includes("does not support transit") || text.includes("transit route"), + "Error message mentions transit limitation", + `got: ${text.slice(0, 200)}` + ); + assert( + text.includes("Japan") || text.includes("region"), + "Error message mentions affected region", + `got: ${text.slice(0, 200)}` + ); + } + + // Test distance matrix with transit in Japan + const dmResult = await sendRequest(session, "tools/call", { + name: "maps_distance_matrix", + arguments: { origins: ["Tokyo Station"], destinations: ["Nagoya Station"], mode: "transit" }, + }); + const dmContent = dmResult?.result?.content ?? []; + assert(dmContent.length > 0, "Transit distance matrix returns content"); + if (dmContent.length > 0) { + const text = dmContent[0]?.text ?? ""; + const isError = dmResult?.result?.isError === true; + // May return error (all-fail) or warning (partial-fail) + if (isError) { + assert( + text.includes("does not support transit") || text.includes("transit route"), + "Distance matrix error mentions transit limitation", + `got: ${text.slice(0, 200)}` + ); + } else { + // Partial success — check for warning in response + try { + const parsed = JSON.parse(text); + const hasWarning = parsed?.warning !== undefined; + const hasNulls = parsed?.distances?.[0]?.[0] === null; + assert( + hasWarning || hasNulls, + "Distance matrix returns warning or null entries for unsupported transit", + `warning=${hasWarning}, nulls=${hasNulls}` + ); + } catch { + assert(false, "Distance matrix returns valid JSON", text.slice(0, 200)); + } + } + } +} + // --------------- Main --------------- async function main() { @@ -994,6 +1084,7 @@ async function main() { await testGeocode(session); await testToolCalls(session); await testPlaceDetailsPhotos(session); + await testTransitErrorMessages(session); await testMultiSession(); } catch (err) { console.error("\n💥 Fatal error:", err);