mcp-travelcode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/build/auth/cli-auth.d.ts +16 -0
- package/build/auth/cli-auth.js +281 -0
- package/build/auth/token-store.d.ts +39 -0
- package/build/auth/token-store.js +113 -0
- package/build/client/api-client.d.ts +42 -0
- package/build/client/api-client.js +235 -0
- package/build/client/types.d.ts +420 -0
- package/build/client/types.js +3 -0
- package/build/config.d.ts +9 -0
- package/build/config.js +30 -0
- package/build/formatters/aerodatabox-formatter.d.ts +6 -0
- package/build/formatters/aerodatabox-formatter.js +246 -0
- package/build/formatters/airline-formatter.d.ts +3 -0
- package/build/formatters/airline-formatter.js +12 -0
- package/build/formatters/airport-formatter.d.ts +4 -0
- package/build/formatters/airport-formatter.js +28 -0
- package/build/formatters/flight-formatter.d.ts +13 -0
- package/build/formatters/flight-formatter.js +100 -0
- package/build/formatters/hotel-formatter.d.ts +4 -0
- package/build/formatters/hotel-formatter.js +55 -0
- package/build/formatters/order-formatter.d.ts +8 -0
- package/build/formatters/order-formatter.js +115 -0
- package/build/http-server.d.ts +16 -0
- package/build/http-server.js +164 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +16 -0
- package/build/polling/flight-poller.d.ts +11 -0
- package/build/polling/flight-poller.js +60 -0
- package/build/server.d.ts +4 -0
- package/build/server.js +54 -0
- package/build/tools/cancel-order.d.ts +9 -0
- package/build/tools/cancel-order.js +24 -0
- package/build/tools/check-order-cancellation.d.ts +8 -0
- package/build/tools/check-order-cancellation.js +22 -0
- package/build/tools/check-order-modification.d.ts +8 -0
- package/build/tools/check-order-modification.js +22 -0
- package/build/tools/create-order.d.ts +75 -0
- package/build/tools/create-order.js +52 -0
- package/build/tools/get-airport-delay-stats.d.ts +9 -0
- package/build/tools/get-airport-delay-stats.js +31 -0
- package/build/tools/get-airport-flights.d.ts +13 -0
- package/build/tools/get-airport-flights.js +78 -0
- package/build/tools/get-airport.d.ts +8 -0
- package/build/tools/get-airport.js +25 -0
- package/build/tools/get-flight-delay-stats.d.ts +8 -0
- package/build/tools/get-flight-delay-stats.js +28 -0
- package/build/tools/get-flight-results.d.ts +16 -0
- package/build/tools/get-flight-results.js +68 -0
- package/build/tools/get-flight-status.d.ts +11 -0
- package/build/tools/get-flight-status.js +42 -0
- package/build/tools/get-hotel-location.d.ts +8 -0
- package/build/tools/get-hotel-location.js +27 -0
- package/build/tools/get-order.d.ts +8 -0
- package/build/tools/get-order.js +22 -0
- package/build/tools/list-orders.d.ts +16 -0
- package/build/tools/list-orders.js +46 -0
- package/build/tools/modify-order.d.ts +10 -0
- package/build/tools/modify-order.js +33 -0
- package/build/tools/search-airlines.d.ts +9 -0
- package/build/tools/search-airlines.js +29 -0
- package/build/tools/search-airports.d.ts +9 -0
- package/build/tools/search-airports.js +30 -0
- package/build/tools/search-flights.d.ts +17 -0
- package/build/tools/search-flights.js +82 -0
- package/build/tools/search-hotel-locations.d.ts +9 -0
- package/build/tools/search-hotel-locations.js +23 -0
- package/build/tools/search-hotels.d.ts +46 -0
- package/build/tools/search-hotels.js +106 -0
- package/package.json +60 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// --- Helpers ---
|
|
2
|
+
function extractLocalTime(endpoint) {
|
|
3
|
+
const time = endpoint.actualTime?.local ||
|
|
4
|
+
endpoint.revisedTime?.local ||
|
|
5
|
+
endpoint.predictedTime?.local ||
|
|
6
|
+
endpoint.scheduledTime?.local;
|
|
7
|
+
if (!time)
|
|
8
|
+
return undefined;
|
|
9
|
+
// Extract HH:mm from ISO string
|
|
10
|
+
const match = time.match(/T(\d{2}:\d{2})/);
|
|
11
|
+
return match ? match[1] : time;
|
|
12
|
+
}
|
|
13
|
+
function extractScheduledTime(endpoint) {
|
|
14
|
+
const time = endpoint.scheduledTime?.local;
|
|
15
|
+
if (!time)
|
|
16
|
+
return undefined;
|
|
17
|
+
const match = time.match(/T(\d{2}:\d{2})/);
|
|
18
|
+
return match ? match[1] : time;
|
|
19
|
+
}
|
|
20
|
+
function airportLabel(endpoint) {
|
|
21
|
+
const iata = endpoint.airport?.iata || "";
|
|
22
|
+
const name = endpoint.airport?.shortName || endpoint.airport?.name || endpoint.airport?.municipalityName || "";
|
|
23
|
+
if (iata && name)
|
|
24
|
+
return `${name} (${iata})`;
|
|
25
|
+
return iata || name || "Unknown";
|
|
26
|
+
}
|
|
27
|
+
function delayText(endpoint) {
|
|
28
|
+
const scheduled = endpoint.scheduledTime?.local;
|
|
29
|
+
const actual = endpoint.actualTime?.local || endpoint.revisedTime?.local || endpoint.predictedTime?.local;
|
|
30
|
+
if (!scheduled || !actual)
|
|
31
|
+
return "";
|
|
32
|
+
const schedDate = new Date(scheduled);
|
|
33
|
+
const actualDate = new Date(actual);
|
|
34
|
+
const diffMin = Math.round((actualDate.getTime() - schedDate.getTime()) / 60000);
|
|
35
|
+
if (diffMin <= 0)
|
|
36
|
+
return "On time";
|
|
37
|
+
return `+${diffMin} min delay`;
|
|
38
|
+
}
|
|
39
|
+
// --- Flight Status ---
|
|
40
|
+
function formatOneFlight(flight) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
const airline = flight.airline?.name || "";
|
|
43
|
+
const flightNum = flight.number || "";
|
|
44
|
+
const status = flight.status || "Unknown";
|
|
45
|
+
lines.push(`${flightNum} ${airline ? `— ${airline}` : ""}`);
|
|
46
|
+
lines.push(`Status: ${status}`);
|
|
47
|
+
lines.push("");
|
|
48
|
+
// Departure
|
|
49
|
+
const depAirport = airportLabel(flight.departure);
|
|
50
|
+
const depScheduled = extractScheduledTime(flight.departure);
|
|
51
|
+
const depActual = extractLocalTime(flight.departure);
|
|
52
|
+
const depDelay = delayText(flight.departure);
|
|
53
|
+
lines.push(`Departure: ${depAirport}`);
|
|
54
|
+
if (depScheduled)
|
|
55
|
+
lines.push(` Scheduled: ${depScheduled}${flight.departure.terminal ? ` (Terminal ${flight.departure.terminal})` : ""}${flight.departure.gate ? `, Gate ${flight.departure.gate}` : ""}`);
|
|
56
|
+
if (depActual && depActual !== depScheduled)
|
|
57
|
+
lines.push(` Actual: ${depActual} ${depDelay}`);
|
|
58
|
+
else if (depDelay)
|
|
59
|
+
lines.push(` ${depDelay}`);
|
|
60
|
+
// Arrival
|
|
61
|
+
const arrAirport = airportLabel(flight.arrival);
|
|
62
|
+
const arrScheduled = extractScheduledTime(flight.arrival);
|
|
63
|
+
const arrActual = extractLocalTime(flight.arrival);
|
|
64
|
+
const arrDelay = delayText(flight.arrival);
|
|
65
|
+
lines.push(`Arrival: ${arrAirport}`);
|
|
66
|
+
if (arrScheduled)
|
|
67
|
+
lines.push(` Scheduled: ${arrScheduled}${flight.arrival.terminal ? ` (Terminal ${flight.arrival.terminal})` : ""}${flight.arrival.gate ? `, Gate ${flight.arrival.gate}` : ""}`);
|
|
68
|
+
if (arrActual && arrActual !== arrScheduled)
|
|
69
|
+
lines.push(` Estimated: ${arrActual} ${arrDelay}`);
|
|
70
|
+
else if (arrDelay)
|
|
71
|
+
lines.push(` ${arrDelay}`);
|
|
72
|
+
if (flight.arrival.baggageBelt) {
|
|
73
|
+
lines.push(` Baggage belt: ${flight.arrival.baggageBelt}`);
|
|
74
|
+
}
|
|
75
|
+
// Aircraft
|
|
76
|
+
if (flight.aircraft) {
|
|
77
|
+
const parts = [];
|
|
78
|
+
if (flight.aircraft.model)
|
|
79
|
+
parts.push(flight.aircraft.model);
|
|
80
|
+
if (flight.aircraft.reg)
|
|
81
|
+
parts.push(`(${flight.aircraft.reg})`);
|
|
82
|
+
if (parts.length > 0) {
|
|
83
|
+
lines.push("");
|
|
84
|
+
lines.push(`Aircraft: ${parts.join(" ")}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Distance
|
|
88
|
+
if (flight.greatCircleDistance?.km) {
|
|
89
|
+
lines.push(`Distance: ${Math.round(flight.greatCircleDistance.km).toLocaleString()} km`);
|
|
90
|
+
}
|
|
91
|
+
// Location (if in-flight)
|
|
92
|
+
if (flight.location?.lat && flight.location?.lon) {
|
|
93
|
+
lines.push("");
|
|
94
|
+
lines.push(`Current position: ${flight.location.lat.toFixed(2)}°, ${flight.location.lon.toFixed(2)}°`);
|
|
95
|
+
if (flight.location.pressureAltFt) {
|
|
96
|
+
lines.push(`Altitude: ${flight.location.pressureAltFt.toLocaleString()} ft`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
export function formatFlightStatus(flights, flightNumber, date) {
|
|
102
|
+
if (!flights || flights.length === 0) {
|
|
103
|
+
return `No flight data found for ${flightNumber} on ${date}. Check the flight number and date.`;
|
|
104
|
+
}
|
|
105
|
+
const lines = [`Flight status: ${flightNumber} | ${date}\n`];
|
|
106
|
+
for (let i = 0; i < flights.length; i++) {
|
|
107
|
+
if (flights.length > 1) {
|
|
108
|
+
lines.push(`--- Leg ${i + 1} ---`);
|
|
109
|
+
}
|
|
110
|
+
lines.push(...formatOneFlight(flights[i]));
|
|
111
|
+
if (i < flights.length - 1)
|
|
112
|
+
lines.push("");
|
|
113
|
+
}
|
|
114
|
+
return lines.join("\n");
|
|
115
|
+
}
|
|
116
|
+
// --- Airport Board ---
|
|
117
|
+
export function formatAirportBoard(flights, airportCode, direction, fromTime, toTime) {
|
|
118
|
+
const label = direction === "departure" ? "Departures" : "Arrivals";
|
|
119
|
+
const lines = [`${label} — ${airportCode} | ${fromTime} to ${toTime}\n`];
|
|
120
|
+
if (!flights || flights.length === 0) {
|
|
121
|
+
lines.push(`No ${direction}s found for this time window.`);
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
lines.push(`${flights.length} flight(s):\n`);
|
|
125
|
+
for (let i = 0; i < flights.length; i++) {
|
|
126
|
+
const f = flights[i];
|
|
127
|
+
const num = f.number || "—";
|
|
128
|
+
const airline = f.airline?.name || f.airline?.iata || "";
|
|
129
|
+
const status = f.status || "";
|
|
130
|
+
let destination;
|
|
131
|
+
let time;
|
|
132
|
+
let terminal;
|
|
133
|
+
let gate;
|
|
134
|
+
if (direction === "departure") {
|
|
135
|
+
destination = airportLabel(f.arrival);
|
|
136
|
+
time = extractScheduledTime(f.departure) || "—";
|
|
137
|
+
terminal = f.departure.terminal || "";
|
|
138
|
+
gate = f.departure.gate || "";
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
destination = airportLabel(f.departure);
|
|
142
|
+
time = extractScheduledTime(f.arrival) || "—";
|
|
143
|
+
terminal = f.arrival.terminal || "";
|
|
144
|
+
gate = f.arrival.gate || "";
|
|
145
|
+
}
|
|
146
|
+
const statusBadge = status ? ` [${status}]` : "";
|
|
147
|
+
const termGate = [terminal ? `T${terminal}` : "", gate ? `Gate ${gate}` : ""].filter(Boolean).join(", ");
|
|
148
|
+
lines.push(`${i + 1}. ${time} | ${num} ${airline} → ${destination}${statusBadge}`);
|
|
149
|
+
if (termGate)
|
|
150
|
+
lines.push(` ${termGate}`);
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
// --- Flight Delay Statistics ---
|
|
155
|
+
export function formatFlightDelayStats(stats, flightNumber) {
|
|
156
|
+
const lines = [`Delay statistics: ${flightNumber}`];
|
|
157
|
+
if (stats.route?.from && stats.route?.to) {
|
|
158
|
+
lines[0] += ` (${stats.route.from} → ${stats.route.to})`;
|
|
159
|
+
}
|
|
160
|
+
lines.push("");
|
|
161
|
+
if (stats.observations !== undefined) {
|
|
162
|
+
lines.push(`Based on ${stats.observations} recent observations\n`);
|
|
163
|
+
}
|
|
164
|
+
if (stats.onTimePercentage !== undefined) {
|
|
165
|
+
lines.push(`On-time (< 15 min): ${stats.onTimePercentage.toFixed(0)}%`);
|
|
166
|
+
}
|
|
167
|
+
if (stats.delayDistribution) {
|
|
168
|
+
for (const bucket of stats.delayDistribution) {
|
|
169
|
+
if (bucket.bucket && bucket.percentage !== undefined) {
|
|
170
|
+
lines.push(`Delayed ${bucket.bucket} min: ${bucket.percentage.toFixed(0)}%`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (stats.cancelledPercentage !== undefined) {
|
|
175
|
+
lines.push(`Cancelled: ${stats.cancelledPercentage.toFixed(1)}%`);
|
|
176
|
+
}
|
|
177
|
+
lines.push("");
|
|
178
|
+
if (stats.averageDelayMin !== undefined) {
|
|
179
|
+
lines.push(`Average delay: ${Math.round(stats.averageDelayMin)} min`);
|
|
180
|
+
}
|
|
181
|
+
if (stats.medianDelayMin !== undefined) {
|
|
182
|
+
lines.push(`Median delay: ${Math.round(stats.medianDelayMin)} min`);
|
|
183
|
+
}
|
|
184
|
+
// Assessment
|
|
185
|
+
lines.push("");
|
|
186
|
+
if (stats.onTimePercentage !== undefined) {
|
|
187
|
+
if (stats.onTimePercentage >= 80) {
|
|
188
|
+
lines.push("Assessment: Very reliable — on time most flights.");
|
|
189
|
+
}
|
|
190
|
+
else if (stats.onTimePercentage >= 60) {
|
|
191
|
+
lines.push("Assessment: Generally reliable — on time about 2 out of 3 flights.");
|
|
192
|
+
}
|
|
193
|
+
else if (stats.onTimePercentage >= 40) {
|
|
194
|
+
lines.push("Assessment: Moderate delays — frequently delayed.");
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
lines.push("Assessment: Unreliable — often delayed significantly.");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return lines.join("\n");
|
|
201
|
+
}
|
|
202
|
+
// --- Airport Delay Statistics ---
|
|
203
|
+
export function formatAirportDelayStats(stats, airportCode, date) {
|
|
204
|
+
const airportName = stats.airport?.name || airportCode;
|
|
205
|
+
const lines = [`Airport delay statistics: ${airportName} (${airportCode}) | ${date}\n`];
|
|
206
|
+
if (stats.departures) {
|
|
207
|
+
const d = stats.departures;
|
|
208
|
+
lines.push("Departures:");
|
|
209
|
+
if (d.averageDelayMin !== undefined)
|
|
210
|
+
lines.push(` Average delay: ${Math.round(d.averageDelayMin)} min`);
|
|
211
|
+
if (d.medianDelayMin !== undefined)
|
|
212
|
+
lines.push(` Median delay: ${Math.round(d.medianDelayMin)} min`);
|
|
213
|
+
if (d.cancellations !== undefined && d.totalFlights !== undefined) {
|
|
214
|
+
const pct = d.totalFlights > 0 ? ((d.cancellations / d.totalFlights) * 100).toFixed(1) : "0";
|
|
215
|
+
lines.push(` Cancellations: ${d.cancellations} of ${d.totalFlights} flights (${pct}%)`);
|
|
216
|
+
}
|
|
217
|
+
lines.push(` Status: ${assessDelay(d.averageDelayMin)}`);
|
|
218
|
+
lines.push("");
|
|
219
|
+
}
|
|
220
|
+
if (stats.arrivals) {
|
|
221
|
+
const a = stats.arrivals;
|
|
222
|
+
lines.push("Arrivals:");
|
|
223
|
+
if (a.averageDelayMin !== undefined)
|
|
224
|
+
lines.push(` Average delay: ${Math.round(a.averageDelayMin)} min`);
|
|
225
|
+
if (a.medianDelayMin !== undefined)
|
|
226
|
+
lines.push(` Median delay: ${Math.round(a.medianDelayMin)} min`);
|
|
227
|
+
if (a.cancellations !== undefined && a.totalFlights !== undefined) {
|
|
228
|
+
const pct = a.totalFlights > 0 ? ((a.cancellations / a.totalFlights) * 100).toFixed(1) : "0";
|
|
229
|
+
lines.push(` Cancellations: ${a.cancellations} of ${a.totalFlights} flights (${pct}%)`);
|
|
230
|
+
}
|
|
231
|
+
lines.push(` Status: ${assessDelay(a.averageDelayMin)}`);
|
|
232
|
+
}
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
function assessDelay(avgDelayMin) {
|
|
236
|
+
if (avgDelayMin === undefined)
|
|
237
|
+
return "No data";
|
|
238
|
+
if (avgDelayMin <= 10)
|
|
239
|
+
return "Normal operations";
|
|
240
|
+
if (avgDelayMin <= 20)
|
|
241
|
+
return "Minor delays";
|
|
242
|
+
if (avgDelayMin <= 40)
|
|
243
|
+
return "Moderate delays";
|
|
244
|
+
return "Severe delays";
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=aerodatabox-formatter.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function formatAirlineList(airlines, query) {
|
|
2
|
+
if (airlines.length === 0) {
|
|
3
|
+
return `No airlines found matching "${query}". Try a different search term (airline name or IATA code).`;
|
|
4
|
+
}
|
|
5
|
+
const lines = [`Found ${airlines.length} airline(s) matching "${query}":\n`];
|
|
6
|
+
for (let i = 0; i < airlines.length; i++) {
|
|
7
|
+
const a = airlines[i];
|
|
8
|
+
lines.push(`${i + 1}. ${a.code} — ${a.title}`);
|
|
9
|
+
}
|
|
10
|
+
return lines.join("\n");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=airline-formatter.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function formatAirportList(airports, query) {
|
|
2
|
+
if (airports.length === 0) {
|
|
3
|
+
return `No airports found matching "${query}". Try a different search term (city name, airport name, or IATA code).`;
|
|
4
|
+
}
|
|
5
|
+
const lines = [`Found ${airports.length} result(s) matching "${query}":\n`];
|
|
6
|
+
for (let i = 0; i < airports.length; i++) {
|
|
7
|
+
const a = airports[i];
|
|
8
|
+
const type = a.isAirport === false ? "City" : "Airport";
|
|
9
|
+
const country = a.country ? `, ${a.country.titleEn}` : "";
|
|
10
|
+
const city = a.city ? `${a.city.titleEn}, ` : "";
|
|
11
|
+
lines.push(`${i + 1}. ${a.code} — ${a.titleEn} [${type}]`);
|
|
12
|
+
lines.push(` ${city}${a.country?.titleEn || ""}`);
|
|
13
|
+
}
|
|
14
|
+
return lines.join("\n");
|
|
15
|
+
}
|
|
16
|
+
export function formatAirportDetail(airport) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
const type = airport.isAirport === false ? "City" : "Airport";
|
|
19
|
+
lines.push(`${type}: ${airport.titleEn} (${airport.code})`);
|
|
20
|
+
if (airport.city) {
|
|
21
|
+
lines.push(`City: ${airport.city.titleEn} (${airport.city.code})`);
|
|
22
|
+
}
|
|
23
|
+
if (airport.country) {
|
|
24
|
+
lines.push(`Country: ${airport.country.titleEn} (${airport.country.code})`);
|
|
25
|
+
}
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=airport-formatter.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FlightSearchResultsResponse } from "../client/types.js";
|
|
2
|
+
interface SearchMeta {
|
|
3
|
+
origin: string;
|
|
4
|
+
destination: string;
|
|
5
|
+
departureDate: string;
|
|
6
|
+
returnDate?: string;
|
|
7
|
+
cabinClass: string;
|
|
8
|
+
passengers: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function formatFlightResults(response: FlightSearchResultsResponse, cacheId: string, meta: SearchMeta, completed: boolean): string;
|
|
11
|
+
export declare function formatFilteredResults(response: FlightSearchResultsResponse, cacheId: string, filterSummary: string): string;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=flight-formatter.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
function formatItinerary(itinerary, label) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
const segments = itinerary.segments;
|
|
4
|
+
if (segments.length === 0)
|
|
5
|
+
return lines;
|
|
6
|
+
const firstSeg = segments[0];
|
|
7
|
+
const lastSeg = segments[segments.length - 1];
|
|
8
|
+
const depTime = firstSeg.departure.at.split("T")[1]?.substring(0, 5) || "";
|
|
9
|
+
const arrTime = lastSeg.arrival.at.split("T")[1]?.substring(0, 5) || "";
|
|
10
|
+
const depCode = firstSeg.departure.iata_code;
|
|
11
|
+
const arrCode = lastSeg.arrival.iata_code;
|
|
12
|
+
const stops = itinerary.transfers;
|
|
13
|
+
const stopsText = stops === 0 ? "Direct" : stops === 1 ? "1 stop" : `${stops} stops`;
|
|
14
|
+
// Flight numbers
|
|
15
|
+
const flightNums = segments.map((s) => `${s.carrier_code}${s.number}`).join(", ");
|
|
16
|
+
lines.push(` ${label}: ${depCode} ${depTime} → ${arrCode} ${arrTime} (${itinerary.duration}) | ${stopsText}`);
|
|
17
|
+
lines.push(` Flights: ${flightNums}`);
|
|
18
|
+
// Show layover airports for connecting flights
|
|
19
|
+
if (stops > 0) {
|
|
20
|
+
const connections = segments.slice(0, -1).map((s) => s.arrival.iata_code);
|
|
21
|
+
lines.push(` Via: ${connections.join(", ")}`);
|
|
22
|
+
}
|
|
23
|
+
return lines;
|
|
24
|
+
}
|
|
25
|
+
function formatOffer(offer, index, currencySign, airlineNames) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
const item = offer.items[0];
|
|
28
|
+
if (!item)
|
|
29
|
+
return `${index}. (no offer data)`;
|
|
30
|
+
const airlineName = airlineNames[item.airline] || item.airline;
|
|
31
|
+
const baggageText = item.includeBaggage
|
|
32
|
+
? item.include_baggage
|
|
33
|
+
? `${item.include_baggage.count} ${item.include_baggage.unit} baggage included`
|
|
34
|
+
: "Baggage included"
|
|
35
|
+
: "No baggage";
|
|
36
|
+
lines.push(`${index}. ${airlineName} (${item.airline}) | ${currencySign}${offer.totalPrice}`);
|
|
37
|
+
// Itineraries
|
|
38
|
+
const itineraries = item.itineraries;
|
|
39
|
+
if (itineraries.length >= 1) {
|
|
40
|
+
lines.push(...formatItinerary(itineraries[0], "Outbound"));
|
|
41
|
+
}
|
|
42
|
+
if (itineraries.length >= 2) {
|
|
43
|
+
lines.push(...formatItinerary(itineraries[1], "Return"));
|
|
44
|
+
}
|
|
45
|
+
lines.push(` ${item.cabinClass} | ${baggageText} | ${item.availableSeats} seats left`);
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
export function formatFlightResults(response, cacheId, meta, completed) {
|
|
49
|
+
const lines = [];
|
|
50
|
+
// Header
|
|
51
|
+
const tripType = meta.returnDate ? "Round trip" : "One way";
|
|
52
|
+
lines.push(`Flight search: ${meta.origin} → ${meta.destination}`);
|
|
53
|
+
lines.push(`${tripType} | ${meta.departureDate}${meta.returnDate ? ` — ${meta.returnDate}` : ""} | ${meta.cabinClass} | ${meta.passengers}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
if (!completed) {
|
|
56
|
+
lines.push("(Search still in progress — showing partial results)");
|
|
57
|
+
lines.push("");
|
|
58
|
+
}
|
|
59
|
+
const total = response.total || response.items.length;
|
|
60
|
+
lines.push(`Found ${total} flight option(s). Showing ${response.items.length}:\n`);
|
|
61
|
+
const airlineNames = response.airlines || {};
|
|
62
|
+
const currencySign = response.currencySign || response.currency || "$";
|
|
63
|
+
for (let i = 0; i < response.items.length; i++) {
|
|
64
|
+
lines.push(formatOffer(response.items[i], i + 1, currencySign, airlineNames));
|
|
65
|
+
if (i < response.items.length - 1)
|
|
66
|
+
lines.push("");
|
|
67
|
+
}
|
|
68
|
+
if (response.items.length === 0) {
|
|
69
|
+
lines.push("No flights found for this route and date. Try different dates or airports.");
|
|
70
|
+
}
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(`Cache ID: ${cacheId}`);
|
|
73
|
+
lines.push("Use get_flight_results with this cache ID to filter, sort, or see more results.");
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
export function formatFilteredResults(response, cacheId, filterSummary) {
|
|
77
|
+
const lines = [];
|
|
78
|
+
lines.push(`Flight results (cache: ${cacheId})`);
|
|
79
|
+
if (filterSummary) {
|
|
80
|
+
lines.push(`Filters: ${filterSummary}`);
|
|
81
|
+
}
|
|
82
|
+
const total = response.total || response.items.length;
|
|
83
|
+
const offset = response.offset || 0;
|
|
84
|
+
const showing = response.items.length;
|
|
85
|
+
lines.push(`Showing ${offset + 1}–${offset + showing} of ${total} result(s)\n`);
|
|
86
|
+
const airlineNames = response.airlines || {};
|
|
87
|
+
const currencySign = response.currencySign || response.currency || "$";
|
|
88
|
+
for (let i = 0; i < response.items.length; i++) {
|
|
89
|
+
lines.push(formatOffer(response.items[i], offset + i + 1, currencySign, airlineNames));
|
|
90
|
+
if (i < response.items.length - 1)
|
|
91
|
+
lines.push("");
|
|
92
|
+
}
|
|
93
|
+
if (response.items.length === 0) {
|
|
94
|
+
lines.push("No flights match these filters. Try relaxing your criteria.");
|
|
95
|
+
}
|
|
96
|
+
lines.push("");
|
|
97
|
+
lines.push(`Cache ID: ${cacheId}`);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=flight-formatter.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { HotelLocationSearchResponse, HotelOffer } from "../client/types.js";
|
|
2
|
+
export declare function formatHotelLocations(data: HotelLocationSearchResponse): string;
|
|
3
|
+
export declare function formatHotelResults(hotels: HotelOffer[], totalCount: number): string;
|
|
4
|
+
//# sourceMappingURL=hotel-formatter.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function formatHotelLocations(data) {
|
|
2
|
+
if (!data.items || data.items.length === 0) {
|
|
3
|
+
return "No locations found.";
|
|
4
|
+
}
|
|
5
|
+
const lines = [`Found ${data.items.length} group(s):\n`];
|
|
6
|
+
for (const group of data.items) {
|
|
7
|
+
lines.push(`--- ${group.type.toUpperCase()}: ${group.text} ---`);
|
|
8
|
+
for (const child of group.children) {
|
|
9
|
+
const idHint = group.type === "hotels" ? `(use location: ${child.id})` : `(use location: ${child.id})`;
|
|
10
|
+
const addr = child.address ? ` — ${child.address}` : "";
|
|
11
|
+
lines.push(` ID: ${child.id} ${idHint} ${child.name}${addr} [${child.countryCode}]`);
|
|
12
|
+
}
|
|
13
|
+
lines.push("");
|
|
14
|
+
}
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
const BOARD_NAMES = {
|
|
18
|
+
RO: "Room Only",
|
|
19
|
+
BI: "Breakfast Included",
|
|
20
|
+
LI: "Lunch Included",
|
|
21
|
+
DI: "Dinner Included",
|
|
22
|
+
HB: "Half Board",
|
|
23
|
+
FB: "Full Board",
|
|
24
|
+
AI: "All Inclusive",
|
|
25
|
+
};
|
|
26
|
+
export function formatHotelResults(hotels, totalCount) {
|
|
27
|
+
if (hotels.length === 0) {
|
|
28
|
+
return totalCount > 0
|
|
29
|
+
? `Found ${totalCount} hotels total, but none in the current page. Try adjusting offset/limit or filters.`
|
|
30
|
+
: "No hotels found matching your criteria.";
|
|
31
|
+
}
|
|
32
|
+
const lines = [`Found ${totalCount} hotels total. Showing ${hotels.length}:\n`];
|
|
33
|
+
for (const hotel of hotels) {
|
|
34
|
+
const starRating = hotel.starRating ?? hotel.stars;
|
|
35
|
+
const stars = starRating ? "★".repeat(starRating) : "";
|
|
36
|
+
const name = hotel.propertyName ?? hotel.name ?? "Unknown Hotel";
|
|
37
|
+
const pricePerNight = hotel.price != null ? `$${hotel.price}` : "N/A";
|
|
38
|
+
const totalPrice = hotel.total != null ? `$${hotel.total}` : "";
|
|
39
|
+
const boardCode = hotel.boardCode ?? hotel.board ?? "";
|
|
40
|
+
const mealName = hotel.meal ?? BOARD_NAMES[boardCode] ?? boardCode;
|
|
41
|
+
const refundable = hotel.refundable;
|
|
42
|
+
const refundText = refundable === true ? "Refundable" : refundable === false ? "Non-refundable" : "";
|
|
43
|
+
const partner = hotel.partnerName ?? "";
|
|
44
|
+
lines.push(`${stars} ${name}`);
|
|
45
|
+
lines.push(` Price: ${pricePerNight}/night${totalPrice ? ` (total: ${totalPrice})` : ""}`);
|
|
46
|
+
if (mealName)
|
|
47
|
+
lines.push(` Meal: ${mealName}`);
|
|
48
|
+
const tags = [refundText, partner].filter(Boolean).join(" | ");
|
|
49
|
+
if (tags)
|
|
50
|
+
lines.push(` ${tags}`);
|
|
51
|
+
lines.push("");
|
|
52
|
+
}
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=hotel-formatter.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OrderList, OrderFull, CancelCheckResponse, CancelResult, ModifyCheckResponse, ModifyResult } from "../client/types.js";
|
|
2
|
+
export declare function formatOrderList(data: OrderList): string;
|
|
3
|
+
export declare function formatOrderDetail(order: OrderFull): string;
|
|
4
|
+
export declare function formatCancelCheck(data: CancelCheckResponse, orderId: number): string;
|
|
5
|
+
export declare function formatCancelResult(data: CancelResult): string;
|
|
6
|
+
export declare function formatModifyCheck(data: ModifyCheckResponse, orderId: number): string;
|
|
7
|
+
export declare function formatModifyResult(data: ModifyResult): string;
|
|
8
|
+
//# sourceMappingURL=order-formatter.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export function formatOrderList(data) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
lines.push(`Orders: showing ${data.items.length} of ${data.total} (offset ${data.offset})`);
|
|
4
|
+
lines.push("");
|
|
5
|
+
if (data.items.length === 0) {
|
|
6
|
+
lines.push("No orders found.");
|
|
7
|
+
return lines.join("\n");
|
|
8
|
+
}
|
|
9
|
+
for (const order of data.items) {
|
|
10
|
+
const date = order.createdAt ? order.createdAt.split("T")[0] : "—";
|
|
11
|
+
lines.push(`#${order.orderId} [${order.code}] | ${order.status} | ${order.totalPrice} ${order.currency} | payment: ${order.paymentStatus} | ${date}`);
|
|
12
|
+
}
|
|
13
|
+
return lines.join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function formatOrderDetail(order) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(`Order #${order.orderId} [${order.code}]`);
|
|
18
|
+
lines.push(`Status: ${order.status} | Payment: ${order.paymentStatus}`);
|
|
19
|
+
lines.push(`Total: ${order.totalPrice} ${order.currency}`);
|
|
20
|
+
if (order.ticketingDeadline) {
|
|
21
|
+
lines.push(`Ticketing deadline: ${order.ticketingDeadline}`);
|
|
22
|
+
}
|
|
23
|
+
// Passengers
|
|
24
|
+
if (order.passengers && order.passengers.length > 0) {
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push("Passengers:");
|
|
27
|
+
for (const p of order.passengers) {
|
|
28
|
+
lines.push(` - ${p.firstName} ${p.lastName} (${p.type}, id: ${p.id})`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Services
|
|
32
|
+
if (order.services && order.services.length > 0) {
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push("Services:");
|
|
35
|
+
for (const s of order.services) {
|
|
36
|
+
const price = s.priceGross > 0 ? ` | ${s.priceGross}` : "";
|
|
37
|
+
const pnr = s.pnr ? ` | PNR: ${s.pnr}` : "";
|
|
38
|
+
const ticket = s.ticketNumber ? ` | Ticket: ${s.ticketNumber}` : "";
|
|
39
|
+
lines.push(` - [${s.id}] ${s.title}`);
|
|
40
|
+
lines.push(` ${s.status} | ${s.date}${price}${pnr}${ticket}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Tickets
|
|
44
|
+
if (order.tickets && order.tickets.length > 0) {
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("Tickets:");
|
|
47
|
+
for (const t of order.tickets) {
|
|
48
|
+
lines.push(` - ${t.ticketNumber} (service ${t.serviceId}) | ${t.status}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Timestamps
|
|
52
|
+
lines.push("");
|
|
53
|
+
if (order.createdAt)
|
|
54
|
+
lines.push(`Created: ${order.createdAt}`);
|
|
55
|
+
if (order.updatedAt)
|
|
56
|
+
lines.push(`Updated: ${order.updatedAt}`);
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
59
|
+
export function formatCancelCheck(data, orderId) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
lines.push(`Cancel check for order #${orderId}`);
|
|
62
|
+
if (!data.cancellable) {
|
|
63
|
+
lines.push(`Result: NOT cancellable`);
|
|
64
|
+
if (data.rules)
|
|
65
|
+
lines.push(`Reason: ${data.rules}`);
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
lines.push(`Result: cancellable`);
|
|
69
|
+
if (data.refund) {
|
|
70
|
+
lines.push(`Refund: ${data.refund.estimatedAmount} ${data.refund.currency} (${data.refund.type})`);
|
|
71
|
+
if (data.refund.penalty > 0) {
|
|
72
|
+
lines.push(`Penalty: ${data.refund.penalty} ${data.refund.currency}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (data.deadline)
|
|
76
|
+
lines.push(`Deadline: ${data.deadline}`);
|
|
77
|
+
if (data.rules)
|
|
78
|
+
lines.push(`Rules: ${data.rules}`);
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
export function formatCancelResult(data) {
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push(`Order #${data.orderId} — ${data.status}`);
|
|
84
|
+
if (data.cancelledAt) {
|
|
85
|
+
lines.push(`Cancelled at: ${data.cancelledAt}`);
|
|
86
|
+
}
|
|
87
|
+
if (data.refund) {
|
|
88
|
+
lines.push(`Refund: ${data.refund.amount} ${data.refund.currency} (${data.refund.type})`);
|
|
89
|
+
if (data.refund.penalty > 0) {
|
|
90
|
+
lines.push(`Penalty: ${data.refund.penalty} ${data.refund.currency}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
export function formatModifyCheck(data, orderId) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push(`Modify check for order #${orderId}`);
|
|
98
|
+
if (!data.modifiable) {
|
|
99
|
+
lines.push(`Result: NOT modifiable`);
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
lines.push(`Result: modifiable`);
|
|
103
|
+
if (data.services && data.services.length > 0) {
|
|
104
|
+
lines.push("");
|
|
105
|
+
for (const s of data.services) {
|
|
106
|
+
lines.push(`Service ${s.serviceId}: ${s.title}`);
|
|
107
|
+
lines.push(` Allowed changes: ${s.allowedChanges.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
export function formatModifyResult(data) {
|
|
113
|
+
return `Order #${data.orderId} — ${data.status}\nUse get_order to check the result after modification completes.`;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=order-formatter.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* HTTP entry point for the TravelCode MCP Server.
|
|
4
|
+
*
|
|
5
|
+
* Supports OAuth 2.1 via MCP spec:
|
|
6
|
+
* - Serves Protected Resource Metadata (RFC 9728) at /.well-known/oauth-protected-resource
|
|
7
|
+
* - Returns 401 with WWW-Authenticate header when Bearer token is missing
|
|
8
|
+
* - Creates per-session McpServer instances using the user's OAuth token
|
|
9
|
+
*
|
|
10
|
+
* The Authorization Server is TravelCode's own OAuth server.
|
|
11
|
+
* Tokens are opaque and validated by TravelCode API on each request.
|
|
12
|
+
*
|
|
13
|
+
* Stdio transport (src/index.ts) remains unchanged for backward compatibility.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=http-server.d.ts.map
|