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 +304 -60
- package/build/client/api-client.d.ts +4 -0
- package/build/client/api-client.js +17 -0
- package/build/client/types.d.ts +65 -0
- package/build/formatters/hotel-formatter.d.ts +2 -1
- package/build/formatters/hotel-formatter.js +46 -0
- package/build/server.js +2 -0
- package/build/tools/get-hotel-offers.d.ts +25 -0
- package/build/tools/get-hotel-offers.js +49 -0
- package/build/tools/search-hotels.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
43
|
-
| `search_airlines` | Find airlines by name or IATA code |
|
|
44
|
-
| `search_flights` | Search
|
|
45
|
-
| `get_flight_results` | Filter
|
|
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
|
|
226
|
+
### 📊 Flight Statistics
|
|
48
227
|
|
|
49
228
|
| Tool | Description |
|
|
50
229
|
|------|-------------|
|
|
51
|
-
| `get_flight_status` | Real-time flight status
|
|
52
|
-
| `get_airport_flights` |
|
|
53
|
-
| `get_flight_delay_stats` | Historical delay statistics for a flight number |
|
|
54
|
-
| `get_airport_delay_stats` | Airport delay and cancellation
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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:**
|
|
306
|
+
**Legacy mode:** Set `TRAVELCODE_API_TOKEN` environment variable to use a static API token (skips OAuth).
|
|
94
307
|
|
|
95
|
-
##
|
|
308
|
+
## Configuration
|
|
96
309
|
|
|
97
|
-
| Variable | Required | Default | Description |
|
|
98
|
-
|
|
99
|
-
| `TRAVELCODE_API_TOKEN` | No | — | Static API token (
|
|
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
|
|
102
|
-
| `TRAVELCODE_POLL_TIMEOUT_MS` | No | 90000 | Flight search timeout
|
|
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
|
-
##
|
|
317
|
+
## Development
|
|
105
318
|
|
|
106
|
-
|
|
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
|
-
|
|
331
|
+
### Project Structure
|
|
109
332
|
|
|
110
|
-
|
|
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
|
-
|
|
346
|
+
## Tech Stack
|
|
113
347
|
|
|
114
|
-
|
|
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
|
-
|
|
354
|
+
## Use Cases
|
|
117
355
|
|
|
118
|
-
|
|
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
|
-
|
|
364
|
+
## Related
|
|
121
365
|
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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}`,
|
package/build/client/types.d.ts
CHANGED
|
@@ -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.
|
|
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