mcp-travelcode 1.0.1 → 1.0.3

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 CHANGED
@@ -1,16 +1,63 @@
1
- # MCP TravelCode
2
-
3
- MCP server for the [TravelCode](https://travel-code.com) corporate travel API. Enables AI assistants (Claude Desktop, Cursor, Claude Code) to search flights & hotels, manage bookings, and track flight status.
1
+ <p align="center">
2
+ <h1 align="center">✈️ MCP TravelCode</h1>
3
+ <p align="center">
4
+ <strong>Model Context Protocol server for travel — flights, hotels, bookings</strong>
5
+ </p>
6
+ <p align="center">
7
+ Give your AI assistant the power to search flights, book hotels, manage orders, and track flight status — all through natural language.
8
+ </p>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/mcp-travelcode"><img src="https://img.shields.io/npm/v/mcp-travelcode.svg" alt="npm version"></a>
13
+ <a href="https://github.com/egorceo/mcp-travelcode/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/mcp-travelcode.svg" alt="MIT License"></a>
14
+ <a href="https://www.npmjs.com/package/mcp-travelcode"><img src="https://img.shields.io/npm/dm/mcp-travelcode.svg" alt="Downloads"></a>
15
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/mcp-travelcode.svg" alt="Node.js"></a>
16
+ <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-compatible-blue" alt="MCP Compatible"></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="#quick-start">Quick Start</a> •
21
+ <a href="#tools-19">Tools</a> •
22
+ <a href="#supported-clients">Clients</a> •
23
+ <a href="#example-conversations">Examples</a> •
24
+ <a href="#authentication">Auth</a> •
25
+ <a href="#development">Development</a>
26
+ </p>
27
+
28
+ ---
29
+
30
+ ## What is this?
31
+
32
+ **MCP TravelCode** is a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that connects AI assistants to the [TravelCode](https://travel-code.com) corporate travel API. It lets AI agents search for flights and hotels, create and manage bookings, check real-time flight status, and access delay statistics — all via natural language conversations.
33
+
34
+ Built for the MCP ecosystem — works with **Claude Desktop**, **Claude Code**, **Cursor**, **Windsurf**, **Cline**, **Continue**, **OpenClaw**, and any MCP-compatible client.
35
+
36
+ ### Key Features
37
+
38
+ - 🔍 **Flight search** — multi-city, one-way, round-trip with cabin class and passenger filters
39
+ - 🏨 **Hotel search** — star rating, meal plans, refundability, price filters with SSE streaming
40
+ - 📊 **Flight status** — real-time tracking with delays, gates, terminals, and aircraft info
41
+ - 📈 **Delay statistics** — historical delay and cancellation data for flights and airports
42
+ - 📋 **Order management** — create, cancel, modify bookings; check cancellation conditions
43
+ - 🔐 **OAuth 2.1 + PKCE** — secure browser-based authentication, auto-refreshing tokens
44
+ - 🌍 **Airport & airline data** — search by name, city, IATA/ICAO code
45
+ - ⚡ **Async polling** — automatic background polling for flight search results
46
+ - 🔄 **Dual transport** — stdio (local) and HTTP/SSE (remote) support
4
47
 
5
48
  ## Quick Start
6
49
 
50
+ ### Install & Authenticate
51
+
7
52
  ```bash
8
- # 1. Authenticate (opens browser, one-time)
53
+ # 1. Authenticate with your TravelCode account (opens browser, one-time)
9
54
  npx mcp-travelcode-auth auth
10
-
11
- # 2. Add to Claude Desktop (claude_desktop_config.json):
12
55
  ```
13
56
 
57
+ ### Claude Desktop
58
+
59
+ Add to your `claude_desktop_config.json`:
60
+
14
61
  ```json
15
62
  {
16
63
  "mcpServers": {
@@ -22,9 +69,7 @@ npx mcp-travelcode-auth auth
22
69
  }
23
70
  ```
24
71
 
25
- ```bash
26
- # 3. Restart Claude Desktop — done!
27
- ```
72
+ Restart Claude Desktop — done! Ask Claude to search flights, book hotels, or check flight status.
28
73
 
29
74
  ### Claude Code
30
75
 
@@ -32,102 +77,301 @@ npx mcp-travelcode-auth auth
32
77
  claude mcp add travelcode -- npx mcp-travelcode
33
78
  ```
34
79
 
80
+ ### ChatGPT Desktop
81
+
82
+ Go to **Settings → Tools → Add MCP Server**, then add:
83
+
84
+ ```json
85
+ {
86
+ "command": "npx",
87
+ "args": ["mcp-travelcode"]
88
+ }
89
+ ```
90
+
91
+ ### Gemini / Google AI Studio
92
+
93
+ Add to your MCP server configuration:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "travelcode": {
99
+ "command": "npx",
100
+ "args": ["mcp-travelcode"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### GitHub Copilot (VS Code)
107
+
108
+ Add to your VS Code `settings.json`:
109
+
110
+ ```json
111
+ {
112
+ "github.copilot.chat.mcpServers": {
113
+ "travelcode": {
114
+ "command": "npx",
115
+ "args": ["mcp-travelcode"]
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Cursor
122
+
123
+ Add to `.cursor/mcp.json` in your project:
124
+
125
+ ```json
126
+ {
127
+ "mcpServers": {
128
+ "travelcode": {
129
+ "command": "npx",
130
+ "args": ["mcp-travelcode"]
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Windsurf / Cline / Continue
137
+
138
+ Add to your MCP configuration (typically `mcp_config.json` or settings):
139
+
140
+ ```json
141
+ {
142
+ "mcpServers": {
143
+ "travelcode": {
144
+ "command": "npx",
145
+ "args": ["mcp-travelcode"]
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Zed
152
+
153
+ Add to your Zed `settings.json`:
154
+
155
+ ```json
156
+ {
157
+ "context_servers": {
158
+ "travelcode": {
159
+ "command": {
160
+ "path": "npx",
161
+ "args": ["mcp-travelcode"]
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### JetBrains IDEs (IntelliJ, WebStorm, PyCharm)
169
+
170
+ Go to **Settings → Tools → AI Assistant → MCP Servers → Add**, set command to `npx` with args `mcp-travelcode`.
171
+
172
+ ### OpenClaw
173
+
174
+ ```yaml
175
+ mcp:
176
+ servers:
177
+ travelcode:
178
+ command: npx
179
+ args: ["mcp-travelcode"]
180
+ ```
181
+
182
+ ### HTTP Transport (Remote / Multi-client)
183
+
184
+ ```bash
185
+ npx mcp-travelcode --http # Start HTTP+SSE server
186
+ # or
187
+ npm run start:http # If installed locally
188
+ ```
189
+
190
+ Connect any MCP client to `http://localhost:3000/mcp` via SSE transport.
191
+
192
+ ## Supported Clients
193
+
194
+ Works with **any MCP-compatible client** — including all major AI assistants, IDEs, and coding tools:
195
+
196
+ | Client | Transport | Status |
197
+ |--------|-----------|--------|
198
+ | [ChatGPT Desktop](https://openai.com/chatgpt/desktop/) | stdio | ✅ Compatible |
199
+ | [Claude Desktop](https://claude.ai/download) | stdio | ✅ Tested |
200
+ | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | stdio | ✅ Tested |
201
+ | [Gemini](https://gemini.google.com) | stdio | ✅ Compatible |
202
+ | [GitHub Copilot](https://github.com/features/copilot) | stdio | ✅ Compatible |
203
+ | [Cursor](https://cursor.com) | stdio | ✅ Tested |
204
+ | [Windsurf](https://codeium.com/windsurf) | stdio | ✅ Compatible |
205
+ | [Cline](https://github.com/cline/cline) | stdio | ✅ Compatible |
206
+ | [Continue](https://continue.dev) | stdio | ✅ Compatible |
207
+ | [Zed](https://zed.dev) | stdio | ✅ Compatible |
208
+ | [JetBrains IDEs](https://www.jetbrains.com/) | stdio | ✅ Compatible |
209
+ | [VS Code](https://code.visualstudio.com/) | stdio | ✅ Compatible |
210
+ | [OpenClaw](https://openclaw.ai) | stdio | ✅ Tested |
211
+ | [MCP Inspector](https://github.com/modelcontextprotocol/inspector) | stdio | ✅ Tested |
212
+ | Any MCP client | stdio / HTTP+SSE | ✅ Compatible |
213
+
35
214
  ## Tools (19)
36
215
 
37
- ### Flight Search & Reference Data
216
+ ### ✈️ Flight Search & Reference Data
38
217
 
39
218
  | Tool | Description |
40
219
  |------|-------------|
41
- | `search_airports` | Find airports by name, city, or IATA code |
42
- | `get_airport` | Get details for a specific airport |
43
- | `search_airlines` | Find airlines by name or IATA code |
44
- | `search_flights` | Search for flights (handles async polling automatically) |
45
- | `get_flight_results` | Filter/sort/paginate existing search results |
220
+ | `search_airports` | Find airports by name, city, or IATA/ICAO code |
221
+ | `get_airport` | Get detailed airport information (location, timezone, terminals) |
222
+ | `search_airlines` | Find airlines by name or IATA/ICAO code |
223
+ | `search_flights` | Search flights one-way, round-trip, multi-city. Handles async polling automatically |
224
+ | `get_flight_results` | Filter, sort, and paginate existing search results |
46
225
 
47
- ### Flight Statistics (AeroDataBox)
226
+ ### 📊 Flight Statistics
48
227
 
49
228
  | Tool | Description |
50
229
  |------|-------------|
51
- | `get_flight_status` | Real-time flight status (delays, gates, terminals, aircraft) |
52
- | `get_airport_flights` | Airport departure/arrival board for a time window |
53
- | `get_flight_delay_stats` | Historical delay statistics for a flight number |
54
- | `get_airport_delay_stats` | Airport delay and cancellation stats for a date |
230
+ | `get_flight_status` | Real-time flight status delays, gates, terminals, aircraft type |
231
+ | `get_airport_flights` | Live airport departure/arrival board for a time window |
232
+ | `get_flight_delay_stats` | Historical on-time performance and delay statistics for a flight number |
233
+ | `get_airport_delay_stats` | Airport-wide delay and cancellation statistics for a date |
55
234
 
56
- ### Hotel Search
235
+ ### 🏨 Hotel Search
57
236
 
58
237
  | Tool | Description |
59
238
  |------|-------------|
60
- | `search_hotel_locations` | Find cities, regions, or hotels by name (returns location IDs) |
239
+ | `search_hotel_locations` | Find cities, regions, or specific hotels by name (returns location IDs for search) |
61
240
  | `get_hotel_location` | Get location details by ID |
62
- | `search_hotels` | Search hotels with filters (stars, price, meal plan, refundability) via SSE stream |
241
+ | `search_hotels` | Search hotels with filters — star rating, price range, meal plan, refundability. Results stream via SSE |
63
242
 
64
- ### Order Management
243
+ ### 📋 Order Management
65
244
 
66
245
  | Tool | Description |
67
246
  |------|-------------|
68
- | `list_orders` | List orders with filtering and pagination |
69
- | `get_order` | Get full order details |
70
- | `create_order` | Book a flight from search results |
71
- | `check_order_cancellation` | Check cancellation conditions and refund estimate |
72
- | `cancel_order` | Cancel an order |
73
- | `check_order_modification` | Check what modifications are allowed |
74
- | `modify_order` | Modify an order (contacts, passport, rebook, baggage) |
247
+ | `list_orders` | List all orders with filtering (status, date range) and pagination |
248
+ | `get_order` | Get full order details — passengers, segments, pricing, ticket numbers |
249
+ | `create_order` | Book a flight from search results — add passengers, contacts, payment |
250
+ | `check_order_cancellation` | Check cancellation conditions, penalties, and refund estimate before canceling |
251
+ | `cancel_order` | Cancel an order with refund processing |
252
+ | `check_order_modification` | Check what modifications are allowed (rebooking, baggage, contacts) |
253
+ | `modify_order` | Modify an order — update contacts, passport info, rebook, add baggage |
254
+
255
+ ## Example Conversations
256
+
257
+ ### Search Flights
258
+
259
+ > **You:** Find me flights from New York to London on April 15, economy class, 2 passengers
260
+ >
261
+ > **AI:** *Uses `search_airports` → `search_flights` → returns formatted flight options with prices, durations, and stops*
262
+
263
+ ### Book a Hotel
264
+
265
+ > **You:** I need a 4-star hotel in Tokyo for May 1-5, 2 adults, with breakfast included
266
+ >
267
+ > **AI:** *Uses `search_hotel_locations` → `search_hotels` with star rating and meal plan filters → shows options*
268
+
269
+ ### Check Flight Status
270
+
271
+ > **You:** Is my flight AA100 on time today?
272
+ >
273
+ > **AI:** *Uses `get_flight_status` → shows real-time departure/arrival times, gate, terminal, any delays*
274
+
275
+ ### Manage Bookings
276
+
277
+ > **You:** Show my recent orders. Can I cancel order #12345?
278
+ >
279
+ > **AI:** *Uses `list_orders` → `check_order_cancellation` → shows cancellation conditions and refund estimate → `cancel_order` if confirmed*
280
+
281
+ ### Flight Delay Analysis
282
+
283
+ > **You:** How often is BA115 delayed? What are the stats?
284
+ >
285
+ > **AI:** *Uses `get_flight_delay_stats` → shows historical on-time percentage, average delays, cancellation rate*
75
286
 
76
287
  ## Authentication
77
288
 
78
- MCP TravelCode uses OAuth 2.1 with PKCE. No API keys to manage just sign in with your TravelCode account.
289
+ MCP TravelCode uses **OAuth 2.1 with PKCE** — the modern standard for secure authentication. No API keys to manage or rotate.
79
290
 
80
291
  ```bash
81
- # Sign in (opens browser)
292
+ # Sign in (opens browser for secure authentication)
82
293
  npx mcp-travelcode-auth auth
83
294
 
84
- # Check token status
295
+ # Check token status and expiration
85
296
  npx mcp-travelcode-auth status
86
297
 
87
- # Sign out
298
+ # Sign out and clear tokens
88
299
  npx mcp-travelcode-auth logout
89
300
  ```
90
301
 
91
- Tokens are stored in `~/.travelcode/tokens.json` and auto-refresh when expired.
302
+ - Tokens are stored in `~/.travelcode/tokens.json`
303
+ - Access tokens auto-refresh when expired — no manual intervention needed
304
+ - Each user authenticates with their own TravelCode account
92
305
 
93
- **Legacy mode:** You can also set `TRAVELCODE_API_TOKEN` environment variable to use a static API token.
306
+ **Legacy mode:** Set `TRAVELCODE_API_TOKEN` environment variable to use a static API token (skips OAuth).
94
307
 
95
- ## Environment Variables
308
+ ## Configuration
96
309
 
97
- | Variable | Required | Default | Description |
98
- |----------|----------|---------|-------------|
99
- | `TRAVELCODE_API_TOKEN` | No | — | Static API token (skips OAuth) |
310
+ | Environment Variable | Required | Default | Description |
311
+ |---------------------|----------|---------|-------------|
312
+ | `TRAVELCODE_API_TOKEN` | No | — | Static API token (bypasses OAuth) |
100
313
  | `TRAVELCODE_API_BASE_URL` | No | `https://api.travel-code.com/v1` | API base URL |
101
- | `TRAVELCODE_POLL_INTERVAL_MS` | No | 2000 | Flight search polling interval (ms) |
102
- | `TRAVELCODE_POLL_TIMEOUT_MS` | No | 90000 | Flight search timeout (ms) |
314
+ | `TRAVELCODE_POLL_INTERVAL_MS` | No | `2000` | Flight search polling interval in milliseconds |
315
+ | `TRAVELCODE_POLL_TIMEOUT_MS` | No | `90000` | Flight search timeout in milliseconds |
103
316
 
104
- ## Example Conversations
317
+ ## Development
105
318
 
106
- > "Find hotels in Dubai for April 15-18, 2 adults, 4-5 stars, all inclusive"
319
+ ```bash
320
+ git clone https://github.com/egorceo/mcp-travelcode.git
321
+ cd mcp-travelcode
322
+ npm install
323
+
324
+ npm run dev # Run with tsx (hot reload)
325
+ npm run build # Compile TypeScript
326
+ npm test # Run tests
327
+ npm run inspect # Test interactively with MCP Inspector
328
+ npm run start:http # Start HTTP+SSE transport server
329
+ ```
107
330
 
108
- Uses `search_hotel_locations` → `search_hotels` with star rating and meal plan filters.
331
+ ### Project Structure
109
332
 
110
- > "Search flights from London to Barcelona on March 15, economy, 2 adults"
333
+ ```
334
+ src/
335
+ ├── index.ts # stdio entry point
336
+ ├── http-server.ts # HTTP+SSE entry point
337
+ ├── server.ts # MCP server setup & tool registration
338
+ ├── config.ts # Environment configuration
339
+ ├── auth/ # OAuth 2.1 PKCE flow & CLI
340
+ ├── client/ # TravelCode API client
341
+ ├── tools/ # 19 MCP tool implementations
342
+ ├── formatters/ # Response formatting
343
+ └── polling/ # Async flight search polling
344
+ ```
111
345
 
112
- Uses `search_airports` → `search_flights` → returns formatted flight options.
346
+ ## Tech Stack
113
347
 
114
- > "Show my orders" / "Cancel order 12345"
348
+ - **TypeScript** full type safety
349
+ - **[@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk)** — official MCP SDK
350
+ - **Zod** — runtime schema validation for all tool inputs
351
+ - **Express 5** — HTTP transport server
352
+ - **Vitest** — testing framework
115
353
 
116
- Uses `list_orders`, `check_order_cancellation` → `cancel_order`.
354
+ ## Use Cases
117
355
 
118
- > "Is flight LO776 on time today?"
356
+ - **Corporate travel management** search and book business travel through AI assistants
357
+ - **Travel agencies** — integrate flight and hotel search into AI-powered agent workflows
358
+ - **Trip planning** — find flights, compare prices, check schedules via natural conversation
359
+ - **Flight monitoring** — track flight status, delays, gate changes in real-time
360
+ - **Travel analytics** — analyze flight delay patterns, airport performance, route statistics
361
+ - **Booking automation** — automate repetitive booking tasks through AI agents
362
+ - **Customer support** — help travelers check bookings, modify orders, handle cancellations
119
363
 
120
- Uses `get_flight_status` to check real-time status.
364
+ ## Related
121
365
 
122
- ## Development
366
+ - [Model Context Protocol](https://modelcontextprotocol.io) — the open standard for AI tool integration
367
+ - [TravelCode](https://travel-code.com) — corporate travel management platform
368
+ - [MCP Servers Directory](https://github.com/modelcontextprotocol/servers) — official MCP server registry
369
+ - [Claude Desktop](https://claude.ai/download) — AI assistant with MCP support
123
370
 
124
- ```bash
125
- npm run dev # Run with tsx (hot reload)
126
- npm run build # Compile TypeScript
127
- npm run inspect # Test with MCP Inspector
128
- npm run start:http # Run HTTP transport (OAuth for browser clients)
129
- ```
371
+ ## Contributing
372
+
373
+ Contributions welcome! Please open an issue or submit a pull request.
130
374
 
131
375
  ## License
132
376
 
133
- MIT
377
+ [MIT](LICENSE) © [Travel Code](https://travel-code.com)
@@ -36,6 +36,10 @@ export declare class TravelCodeApiClient {
36
36
  * GET with accessToken as query parameter (used by hotel location endpoints).
37
37
  */
38
38
  getWithTokenParam<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
39
+ /**
40
+ * POST with accessToken in body (used by hotel offers endpoint).
41
+ */
42
+ postWithTokenParam<T>(path: string, body: Record<string, unknown>): Promise<T>;
39
43
  private headers;
40
44
  private handleResponse;
41
45
  }
@@ -197,6 +197,23 @@ export class TravelCodeApiClient {
197
197
  });
198
198
  return this.handleResponse(response);
199
199
  }
200
+ /**
201
+ * POST with accessToken in body (used by hotel offers endpoint).
202
+ */
203
+ async postWithTokenParam(path, body) {
204
+ await this.ensureValidToken();
205
+ const bodyWithToken = { ...body, accessToken: this.token };
206
+ const response = await fetch(`${this.baseUrl}${path}`, {
207
+ method: "POST",
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ "X-Source": "mcp-server",
211
+ Accept: "application/json",
212
+ },
213
+ body: JSON.stringify(bodyWithToken),
214
+ });
215
+ return this.handleResponse(response);
216
+ }
200
217
  headers() {
201
218
  return {
202
219
  Authorization: `Bearer ${this.token}`,
@@ -412,6 +412,71 @@ export interface HotelSSECompleted {
412
412
  hotels: HotelOffer[];
413
413
  cacheKey: string;
414
414
  }
415
+ export interface HotelOfferPrice {
416
+ currency: string;
417
+ net: number;
418
+ gross: number;
419
+ total: number;
420
+ markup: number;
421
+ nights: number;
422
+ rooms: number;
423
+ nightly: number;
424
+ extra?: number;
425
+ totalWithExtra?: number;
426
+ deposit?: number | null;
427
+ }
428
+ export interface HotelOfferCancelPolicy {
429
+ refundable: boolean;
430
+ title: string;
431
+ description?: string;
432
+ fullyRefundable: boolean;
433
+ }
434
+ export interface HotelOfferRoom {
435
+ occupancyRefId: number;
436
+ code: string;
437
+ description: string;
438
+ }
439
+ export interface HotelOfferRate {
440
+ partnerId: number;
441
+ boardName: string;
442
+ price: HotelOfferPrice;
443
+ cancelPolicy: HotelOfferCancelPolicy;
444
+ rooms: HotelOfferRoom[];
445
+ externalId: string;
446
+ quoteKey: string;
447
+ }
448
+ export interface HotelOfferRoomGroup {
449
+ content: {
450
+ area?: string | null;
451
+ views?: string | null;
452
+ photos?: string[];
453
+ };
454
+ rates: HotelOfferRate[];
455
+ }
456
+ export interface HotelPropertyDescription {
457
+ title: string;
458
+ text: string;
459
+ }
460
+ export interface HotelProperty {
461
+ id?: string;
462
+ gId?: number;
463
+ name: string;
464
+ starRating?: number;
465
+ address?: string;
466
+ heroImage?: string;
467
+ images?: Array<{
468
+ url: string;
469
+ }>;
470
+ description?: HotelPropertyDescription[];
471
+ latitude?: number;
472
+ longitude?: number;
473
+ }
474
+ export interface HotelOffersResponse {
475
+ offersKey: string;
476
+ property: HotelProperty;
477
+ offers: Record<string, HotelOfferRoomGroup>;
478
+ bronevikId?: number;
479
+ }
415
480
  export interface ApiErrorResponse {
416
481
  code: number;
417
482
  message?: string;
@@ -1,4 +1,5 @@
1
- import { HotelLocationSearchResponse, HotelOffer } from "../client/types.js";
1
+ import { HotelLocationSearchResponse, HotelOffer, HotelOffersResponse } from "../client/types.js";
2
2
  export declare function formatHotelLocations(data: HotelLocationSearchResponse): string;
3
3
  export declare function formatHotelResults(hotels: HotelOffer[], totalCount: number): string;
4
+ export declare function formatHotelOffers(data: HotelOffersResponse): string;
4
5
  //# sourceMappingURL=hotel-formatter.d.ts.map
@@ -52,4 +52,50 @@ export function formatHotelResults(hotels, totalCount) {
52
52
  }
53
53
  return lines.join("\n");
54
54
  }
55
+ export function formatHotelOffers(data) {
56
+ const prop = data.property;
57
+ const stars = prop.starRating ? "★".repeat(prop.starRating) : "";
58
+ const lines = [
59
+ `${stars} ${prop.name}`,
60
+ prop.address ? `Address: ${prop.address}` : "",
61
+ ].filter(Boolean);
62
+ // Descriptions
63
+ if (prop.description && prop.description.length > 0) {
64
+ for (const desc of prop.description.slice(0, 2)) {
65
+ const text = desc.text.length > 200 ? desc.text.slice(0, 200) + "..." : desc.text;
66
+ lines.push(`${desc.title}: ${text}`);
67
+ }
68
+ }
69
+ const roomGroups = Object.entries(data.offers);
70
+ let totalRates = 0;
71
+ for (const [, group] of roomGroups) {
72
+ totalRates += group.rates.length;
73
+ }
74
+ lines.push("");
75
+ lines.push(`${roomGroups.length} room types, ${totalRates} rates total:`);
76
+ lines.push("");
77
+ for (const [roomName, group] of roomGroups) {
78
+ const cheapest = group.rates.reduce((min, r) => (r.price.nightly < min.price.nightly ? r : min), group.rates[0]);
79
+ if (!cheapest)
80
+ continue;
81
+ const refundable = group.rates.some((r) => r.cancelPolicy.refundable);
82
+ const boards = [...new Set(group.rates.map((r) => r.boardName))].join(", ");
83
+ lines.push(`--- ${roomName} (${group.rates.length} offers) ---`);
84
+ lines.push(` From: ${cheapest.price.nightly} ${cheapest.price.currency}/night (total: ${cheapest.price.total} for ${cheapest.price.nights} night(s))`);
85
+ lines.push(` Meal options: ${boards}`);
86
+ lines.push(` ${refundable ? "Refundable options available" : "Non-refundable"}`);
87
+ // Show top 3 rates
88
+ const sorted = [...group.rates].sort((a, b) => a.price.nightly - b.price.nightly);
89
+ for (const rate of sorted.slice(0, 3)) {
90
+ const cancel = rate.cancelPolicy.refundable ? "Refundable" : "Non-refundable";
91
+ lines.push(` ${rate.price.nightly} ${rate.price.currency}/night | ${rate.boardName} | ${cancel}`);
92
+ }
93
+ if (sorted.length > 3) {
94
+ lines.push(` ... and ${sorted.length - 3} more offers`);
95
+ }
96
+ lines.push("");
97
+ }
98
+ lines.push(`offersKey: ${data.offersKey}`);
99
+ return lines.join("\n");
100
+ }
55
101
  //# sourceMappingURL=hotel-formatter.js.map
package/build/server.js CHANGED
@@ -19,6 +19,7 @@ import { registerModifyOrder } from "./tools/modify-order.js";
19
19
  import { registerSearchHotelLocations } from "./tools/search-hotel-locations.js";
20
20
  import { registerGetHotelLocation } from "./tools/get-hotel-location.js";
21
21
  import { registerSearchHotels } from "./tools/search-hotels.js";
22
+ import { registerGetHotelOffers } from "./tools/get-hotel-offers.js";
22
23
  export function createServer(config) {
23
24
  const server = new McpServer({
24
25
  name: "TravelCode",
@@ -49,6 +50,7 @@ export function createServer(config) {
49
50
  registerSearchHotelLocations(server, client);
50
51
  registerGetHotelLocation(server, client);
51
52
  registerSearchHotels(server, client);
53
+ registerGetHotelOffers(server, client);
52
54
  return server;
53
55
  }
54
56
  //# sourceMappingURL=server.js.map
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { TravelCodeApiClient } from "../client/api-client.js";
4
+ export declare const getHotelOffersSchema: {
5
+ id: z.ZodNumber;
6
+ checkin: z.ZodString;
7
+ checkout: z.ZodString;
8
+ country_code: z.ZodString;
9
+ guests: z.ZodArray<z.ZodObject<{
10
+ adults: z.ZodNumber;
11
+ children: z.ZodOptional<z.ZodNumber>;
12
+ childrenAges: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ adults: number;
15
+ children?: number | undefined;
16
+ childrenAges?: number[] | undefined;
17
+ }, {
18
+ adults: number;
19
+ children?: number | undefined;
20
+ childrenAges?: number[] | undefined;
21
+ }>, "many">;
22
+ location: z.ZodOptional<z.ZodNumber>;
23
+ };
24
+ export declare function registerGetHotelOffers(server: McpServer, client: TravelCodeApiClient): void;
25
+ //# sourceMappingURL=get-hotel-offers.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { formatHotelOffers } from "../formatters/hotel-formatter.js";
3
+ const guestSchema = z.object({
4
+ adults: z.number().int().min(1).max(4).describe("Number of adults (1-4)"),
5
+ children: z.number().int().min(0).max(3).optional().describe("Number of children (0-3)"),
6
+ childrenAges: z
7
+ .array(z.number().int().min(0).max(17))
8
+ .optional()
9
+ .describe("Array of child ages (0-17), required if children > 0"),
10
+ });
11
+ export const getHotelOffersSchema = {
12
+ id: z.number().int().describe("Hotel giataId (from search_hotels results)"),
13
+ checkin: z.string().describe("Check-in date (YYYY-MM-DD)"),
14
+ checkout: z.string().describe("Check-out date (YYYY-MM-DD)"),
15
+ country_code: z.string().describe("Guest nationality ISO code (e.g. BY, RU, US)"),
16
+ guests: z.array(guestSchema).min(1).describe("Array of rooms, each with adults count and optional children"),
17
+ location: z
18
+ .number()
19
+ .int()
20
+ .optional()
21
+ .describe("Location ID from prior search — enables cache reuse for faster results"),
22
+ };
23
+ export function registerGetHotelOffers(server, client) {
24
+ server.tool("get_hotel_offers", "Get all available rooms and rates for a specific hotel from all suppliers. Returns room types, prices, meal plans, and cancellation policies. The hotel giataId comes from search_hotels results — do not explain this to the user, just chain the calls silently.", getHotelOffersSchema, async ({ id, checkin, checkout, country_code, guests, location }) => {
25
+ try {
26
+ const body = {
27
+ id,
28
+ checkin,
29
+ checkout,
30
+ countryCode: country_code,
31
+ guests,
32
+ };
33
+ if (location !== undefined) {
34
+ body.location = location;
35
+ }
36
+ const data = await client.postWithTokenParam("/search/hotels/offers", body);
37
+ return {
38
+ content: [{ type: "text", text: formatHotelOffers(data) }],
39
+ };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ content: [{ type: "text", text: `Error getting hotel offers: ${error.message}` }],
44
+ isError: true,
45
+ };
46
+ }
47
+ });
48
+ }
49
+ //# sourceMappingURL=get-hotel-offers.js.map
@@ -39,7 +39,7 @@ export const searchHotelsSchema = {
39
39
  filter: filterSchema,
40
40
  };
41
41
  export function registerSearchHotels(server, client) {
42
- server.tool("search_hotels", "Search hotels by location, dates, and guests. First use search_hotel_locations to get a location ID. Returns hotel offers with prices, star ratings, and meal plans. Supports filtering by stars, price, meal plan, and refundability.", searchHotelsSchema, async ({ location, checkin, checkout, country_code, guests, sort, offset, limit, filter }) => {
42
+ server.tool("search_hotels", "Search hotels by location, dates, and guests. Requires a location ID from search_hotel_locations chain the calls silently without explaining intermediate steps to the user. Returns hotel offers with prices, star ratings, and meal plans. Supports filtering by stars, price, meal plan, and refundability.", searchHotelsSchema, async ({ location, checkin, checkout, country_code, guests, sort, offset, limit, filter }) => {
43
43
  try {
44
44
  const body = {
45
45
  location,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-travelcode",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for TravelCode — flights, hotels, orders. Search flights & hotels, manage bookings via AI assistants.",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",