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.
Files changed (70) hide show
  1. package/README.md +133 -0
  2. package/build/auth/cli-auth.d.ts +16 -0
  3. package/build/auth/cli-auth.js +281 -0
  4. package/build/auth/token-store.d.ts +39 -0
  5. package/build/auth/token-store.js +113 -0
  6. package/build/client/api-client.d.ts +42 -0
  7. package/build/client/api-client.js +235 -0
  8. package/build/client/types.d.ts +420 -0
  9. package/build/client/types.js +3 -0
  10. package/build/config.d.ts +9 -0
  11. package/build/config.js +30 -0
  12. package/build/formatters/aerodatabox-formatter.d.ts +6 -0
  13. package/build/formatters/aerodatabox-formatter.js +246 -0
  14. package/build/formatters/airline-formatter.d.ts +3 -0
  15. package/build/formatters/airline-formatter.js +12 -0
  16. package/build/formatters/airport-formatter.d.ts +4 -0
  17. package/build/formatters/airport-formatter.js +28 -0
  18. package/build/formatters/flight-formatter.d.ts +13 -0
  19. package/build/formatters/flight-formatter.js +100 -0
  20. package/build/formatters/hotel-formatter.d.ts +4 -0
  21. package/build/formatters/hotel-formatter.js +55 -0
  22. package/build/formatters/order-formatter.d.ts +8 -0
  23. package/build/formatters/order-formatter.js +115 -0
  24. package/build/http-server.d.ts +16 -0
  25. package/build/http-server.js +164 -0
  26. package/build/index.d.ts +3 -0
  27. package/build/index.js +16 -0
  28. package/build/polling/flight-poller.d.ts +11 -0
  29. package/build/polling/flight-poller.js +60 -0
  30. package/build/server.d.ts +4 -0
  31. package/build/server.js +54 -0
  32. package/build/tools/cancel-order.d.ts +9 -0
  33. package/build/tools/cancel-order.js +24 -0
  34. package/build/tools/check-order-cancellation.d.ts +8 -0
  35. package/build/tools/check-order-cancellation.js +22 -0
  36. package/build/tools/check-order-modification.d.ts +8 -0
  37. package/build/tools/check-order-modification.js +22 -0
  38. package/build/tools/create-order.d.ts +75 -0
  39. package/build/tools/create-order.js +52 -0
  40. package/build/tools/get-airport-delay-stats.d.ts +9 -0
  41. package/build/tools/get-airport-delay-stats.js +31 -0
  42. package/build/tools/get-airport-flights.d.ts +13 -0
  43. package/build/tools/get-airport-flights.js +78 -0
  44. package/build/tools/get-airport.d.ts +8 -0
  45. package/build/tools/get-airport.js +25 -0
  46. package/build/tools/get-flight-delay-stats.d.ts +8 -0
  47. package/build/tools/get-flight-delay-stats.js +28 -0
  48. package/build/tools/get-flight-results.d.ts +16 -0
  49. package/build/tools/get-flight-results.js +68 -0
  50. package/build/tools/get-flight-status.d.ts +11 -0
  51. package/build/tools/get-flight-status.js +42 -0
  52. package/build/tools/get-hotel-location.d.ts +8 -0
  53. package/build/tools/get-hotel-location.js +27 -0
  54. package/build/tools/get-order.d.ts +8 -0
  55. package/build/tools/get-order.js +22 -0
  56. package/build/tools/list-orders.d.ts +16 -0
  57. package/build/tools/list-orders.js +46 -0
  58. package/build/tools/modify-order.d.ts +10 -0
  59. package/build/tools/modify-order.js +33 -0
  60. package/build/tools/search-airlines.d.ts +9 -0
  61. package/build/tools/search-airlines.js +29 -0
  62. package/build/tools/search-airports.d.ts +9 -0
  63. package/build/tools/search-airports.js +30 -0
  64. package/build/tools/search-flights.d.ts +17 -0
  65. package/build/tools/search-flights.js +82 -0
  66. package/build/tools/search-hotel-locations.d.ts +9 -0
  67. package/build/tools/search-hotel-locations.js +23 -0
  68. package/build/tools/search-hotels.d.ts +46 -0
  69. package/build/tools/search-hotels.js +106 -0
  70. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,133 @@
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.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # 1. Authenticate (opens browser, one-time)
9
+ npx mcp-travelcode-auth auth
10
+
11
+ # 2. Add to Claude Desktop (claude_desktop_config.json):
12
+ ```
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "travelcode": {
18
+ "command": "npx",
19
+ "args": ["mcp-travelcode"]
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ```bash
26
+ # 3. Restart Claude Desktop — done!
27
+ ```
28
+
29
+ ### Claude Code
30
+
31
+ ```bash
32
+ claude mcp add travelcode -- npx mcp-travelcode
33
+ ```
34
+
35
+ ## Tools (19)
36
+
37
+ ### Flight Search & Reference Data
38
+
39
+ | Tool | Description |
40
+ |------|-------------|
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 |
46
+
47
+ ### Flight Statistics (AeroDataBox)
48
+
49
+ | Tool | Description |
50
+ |------|-------------|
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 |
55
+
56
+ ### Hotel Search
57
+
58
+ | Tool | Description |
59
+ |------|-------------|
60
+ | `search_hotel_locations` | Find cities, regions, or hotels by name (returns location IDs) |
61
+ | `get_hotel_location` | Get location details by ID |
62
+ | `search_hotels` | Search hotels with filters (stars, price, meal plan, refundability) via SSE stream |
63
+
64
+ ### Order Management
65
+
66
+ | Tool | Description |
67
+ |------|-------------|
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) |
75
+
76
+ ## Authentication
77
+
78
+ MCP TravelCode uses OAuth 2.1 with PKCE. No API keys to manage — just sign in with your TravelCode account.
79
+
80
+ ```bash
81
+ # Sign in (opens browser)
82
+ npx mcp-travelcode-auth auth
83
+
84
+ # Check token status
85
+ npx mcp-travelcode-auth status
86
+
87
+ # Sign out
88
+ npx mcp-travelcode-auth logout
89
+ ```
90
+
91
+ Tokens are stored in `~/.travelcode/tokens.json` and auto-refresh when expired.
92
+
93
+ **Legacy mode:** You can also set `TRAVELCODE_API_TOKEN` environment variable to use a static API token.
94
+
95
+ ## Environment Variables
96
+
97
+ | Variable | Required | Default | Description |
98
+ |----------|----------|---------|-------------|
99
+ | `TRAVELCODE_API_TOKEN` | No | — | Static API token (skips OAuth) |
100
+ | `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) |
103
+
104
+ ## Example Conversations
105
+
106
+ > "Find hotels in Dubai for April 15-18, 2 adults, 4-5 stars, all inclusive"
107
+
108
+ Uses `search_hotel_locations` → `search_hotels` with star rating and meal plan filters.
109
+
110
+ > "Search flights from London to Barcelona on March 15, economy, 2 adults"
111
+
112
+ Uses `search_airports` → `search_flights` → returns formatted flight options.
113
+
114
+ > "Show my orders" / "Cancel order 12345"
115
+
116
+ Uses `list_orders`, `check_order_cancellation` → `cancel_order`.
117
+
118
+ > "Is flight LO776 on time today?"
119
+
120
+ Uses `get_flight_status` to check real-time status.
121
+
122
+ ## Development
123
+
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
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI OAuth flow for obtaining TravelCode API tokens.
4
+ *
5
+ * Usage: npx mcp-travelcode auth
6
+ *
7
+ * Flow:
8
+ * 1. Register client via DCR (if not already registered)
9
+ * 2. Generate PKCE code_verifier + code_challenge
10
+ * 3. Open browser to /oauth/authorize
11
+ * 4. Start local HTTP server to catch redirect callback
12
+ * 5. Exchange code for tokens
13
+ * 6. Save tokens to ~/.travelcode/tokens.json
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=cli-auth.d.ts.map
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI OAuth flow for obtaining TravelCode API tokens.
4
+ *
5
+ * Usage: npx mcp-travelcode auth
6
+ *
7
+ * Flow:
8
+ * 1. Register client via DCR (if not already registered)
9
+ * 2. Generate PKCE code_verifier + code_challenge
10
+ * 3. Open browser to /oauth/authorize
11
+ * 4. Start local HTTP server to catch redirect callback
12
+ * 5. Exchange code for tokens
13
+ * 6. Save tokens to ~/.travelcode/tokens.json
14
+ */
15
+ import { createServer } from "node:http";
16
+ import { randomBytes, createHash } from "node:crypto";
17
+ import { URL, URLSearchParams } from "node:url";
18
+ import { loadClient, saveClient, saveTokens, loadTokens, clearTokens, } from "./token-store.js";
19
+ const DEFAULT_ISSUER = "https://travel-code.com";
20
+ const CALLBACK_PORT = 19284; // random-ish port unlikely to conflict
21
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/callback`;
22
+ const SCOPES = [
23
+ "flights:search",
24
+ "flights:status",
25
+ "flights:stats",
26
+ "airports:read",
27
+ "airlines:read",
28
+ "orders:read",
29
+ "orders:write",
30
+ ].join(" ");
31
+ // --- PKCE ---
32
+ function generateCodeVerifier() {
33
+ return randomBytes(32).toString("base64url");
34
+ }
35
+ function generateCodeChallenge(verifier) {
36
+ return createHash("sha256").update(verifier).digest("base64url");
37
+ }
38
+ function generateState() {
39
+ return randomBytes(16).toString("base64url");
40
+ }
41
+ // --- DCR ---
42
+ async function registerClient(issuer) {
43
+ const existing = await loadClient(issuer);
44
+ if (existing) {
45
+ console.log(`Using existing client: ${existing.client_id}`);
46
+ return existing;
47
+ }
48
+ console.log("Registering OAuth client...");
49
+ const response = await fetch(`${issuer}/oauth/register`, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({
53
+ client_name: "MCP TravelCode CLI",
54
+ redirect_uris: [REDIRECT_URI],
55
+ grant_types: ["authorization_code", "refresh_token"],
56
+ response_types: ["code"],
57
+ token_endpoint_auth_method: "none",
58
+ }),
59
+ });
60
+ if (!response.ok) {
61
+ const text = await response.text();
62
+ throw new Error(`Client registration failed (${response.status}): ${text}`);
63
+ }
64
+ const data = (await response.json());
65
+ const client = {
66
+ client_id: data.client_id,
67
+ client_name: data.client_name,
68
+ redirect_uris: data.redirect_uris,
69
+ issuer,
70
+ };
71
+ await saveClient(client);
72
+ console.log(`Client registered: ${client.client_id}`);
73
+ return client;
74
+ }
75
+ // --- Token exchange ---
76
+ async function exchangeCodeForTokens(issuer, clientId, code, codeVerifier) {
77
+ const response = await fetch(`${issuer}/oauth/token`, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
80
+ body: new URLSearchParams({
81
+ grant_type: "authorization_code",
82
+ code,
83
+ redirect_uri: REDIRECT_URI,
84
+ client_id: clientId,
85
+ code_verifier: codeVerifier,
86
+ }),
87
+ });
88
+ if (!response.ok) {
89
+ const text = await response.text();
90
+ throw new Error(`Token exchange failed (${response.status}): ${text}`);
91
+ }
92
+ const data = (await response.json());
93
+ const now = Math.floor(Date.now() / 1000);
94
+ return {
95
+ access_token: data.access_token,
96
+ refresh_token: data.refresh_token,
97
+ expires_at: now + data.expires_in,
98
+ scope: data.scope,
99
+ client_id: clientId,
100
+ issuer,
101
+ };
102
+ }
103
+ // --- Open browser ---
104
+ async function openBrowser(url) {
105
+ const { platform } = await import("node:os");
106
+ const { exec } = await import("node:child_process");
107
+ const os = platform();
108
+ let command;
109
+ if (os === "darwin") {
110
+ command = `open "${url}"`;
111
+ }
112
+ else if (os === "win32") {
113
+ command = `start "${url}"`;
114
+ }
115
+ else {
116
+ command = `xdg-open "${url}"`;
117
+ }
118
+ exec(command, (error) => {
119
+ if (error) {
120
+ console.log(`\nCould not open browser automatically. Please open this URL manually:\n${url}\n`);
121
+ }
122
+ });
123
+ }
124
+ // --- Callback server ---
125
+ function waitForCallback(expectedState) {
126
+ return new Promise((resolve, reject) => {
127
+ const timeout = setTimeout(() => {
128
+ server.close();
129
+ reject(new Error("Authorization timed out (5 minutes). Please try again."));
130
+ }, 5 * 60 * 1000);
131
+ const server = createServer((req, res) => {
132
+ const url = new URL(req.url || "/", `http://localhost:${CALLBACK_PORT}`);
133
+ if (url.pathname !== "/callback") {
134
+ res.writeHead(404);
135
+ res.end("Not found");
136
+ return;
137
+ }
138
+ const error = url.searchParams.get("error");
139
+ const errorDescription = url.searchParams.get("error_description");
140
+ const code = url.searchParams.get("code");
141
+ const state = url.searchParams.get("state");
142
+ if (error) {
143
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
144
+ res.end(`
145
+ <html><body style="font-family: sans-serif; text-align: center; margin-top: 50px;">
146
+ <h2>Authorization failed</h2>
147
+ <p>${errorDescription || error}</p>
148
+ <p>You can close this window.</p>
149
+ </body></html>
150
+ `);
151
+ clearTimeout(timeout);
152
+ server.close();
153
+ reject(new Error(`Authorization denied: ${errorDescription || error}`));
154
+ return;
155
+ }
156
+ if (!code || state !== expectedState) {
157
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
158
+ res.end(`
159
+ <html><body style="font-family: sans-serif; text-align: center; margin-top: 50px;">
160
+ <h2>Invalid callback</h2>
161
+ <p>Missing code or state mismatch.</p>
162
+ </body></html>
163
+ `);
164
+ clearTimeout(timeout);
165
+ server.close();
166
+ reject(new Error("Invalid callback: missing code or state mismatch"));
167
+ return;
168
+ }
169
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
170
+ res.end(`
171
+ <html><body style="font-family: sans-serif; text-align: center; margin-top: 50px;">
172
+ <h2>Authorization successful!</h2>
173
+ <p>You can close this window and return to the terminal.</p>
174
+ </body></html>
175
+ `);
176
+ clearTimeout(timeout);
177
+ server.close();
178
+ resolve({ code });
179
+ });
180
+ server.listen(CALLBACK_PORT, () => {
181
+ // server ready
182
+ });
183
+ server.on("error", (err) => {
184
+ clearTimeout(timeout);
185
+ reject(new Error(`Failed to start callback server on port ${CALLBACK_PORT}: ${err.message}`));
186
+ });
187
+ });
188
+ }
189
+ // --- Main ---
190
+ async function authCommand() {
191
+ const issuer = process.env.OAUTH_ISSUER || DEFAULT_ISSUER;
192
+ console.log("TravelCode OAuth Authorization");
193
+ console.log(`Issuer: ${issuer}`);
194
+ console.log("");
195
+ // 1. Register client (or reuse existing)
196
+ const client = await registerClient(issuer);
197
+ // 2. Generate PKCE
198
+ const codeVerifier = generateCodeVerifier();
199
+ const codeChallenge = generateCodeChallenge(codeVerifier);
200
+ const state = generateState();
201
+ // 3. Build authorization URL
202
+ const authUrl = new URL(`${issuer}/oauth/authorize`);
203
+ authUrl.searchParams.set("response_type", "code");
204
+ authUrl.searchParams.set("client_id", client.client_id);
205
+ authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
206
+ authUrl.searchParams.set("scope", SCOPES);
207
+ authUrl.searchParams.set("state", state);
208
+ authUrl.searchParams.set("code_challenge", codeChallenge);
209
+ authUrl.searchParams.set("code_challenge_method", "S256");
210
+ // 4. Start callback server and open browser
211
+ console.log("Opening browser for authorization...");
212
+ const callbackPromise = waitForCallback(state);
213
+ await openBrowser(authUrl.toString());
214
+ console.log("Waiting for authorization...\n");
215
+ // 5. Wait for callback
216
+ const { code } = await callbackPromise;
217
+ console.log("Authorization code received. Exchanging for tokens...");
218
+ // 6. Exchange code for tokens
219
+ const tokens = await exchangeCodeForTokens(issuer, client.client_id, code, codeVerifier);
220
+ await saveTokens(tokens);
221
+ console.log("");
222
+ console.log("Authentication successful!");
223
+ console.log(` Access token expires: ${new Date(tokens.expires_at * 1000).toLocaleString()}`);
224
+ console.log(` Scopes: ${tokens.scope}`);
225
+ console.log(` Tokens saved to: ~/.travelcode/tokens.json`);
226
+ console.log("");
227
+ console.log("You can now use the MCP server. It will automatically use the saved token.");
228
+ }
229
+ async function statusCommand() {
230
+ const tokens = await loadTokens();
231
+ if (!tokens) {
232
+ console.log("Not authenticated. Run: npx mcp-travelcode auth");
233
+ return;
234
+ }
235
+ const now = Math.floor(Date.now() / 1000);
236
+ const expired = tokens.expires_at <= now;
237
+ const expiresIn = tokens.expires_at - now;
238
+ console.log("TravelCode Auth Status");
239
+ console.log(` Issuer: ${tokens.issuer}`);
240
+ console.log(` Client ID: ${tokens.client_id}`);
241
+ console.log(` Scopes: ${tokens.scope}`);
242
+ console.log(` Expires: ${new Date(tokens.expires_at * 1000).toLocaleString()}`);
243
+ console.log(` Status: ${expired ? "EXPIRED" : `valid (${Math.floor(expiresIn / 60)} min left)`}`);
244
+ console.log(` Has refresh token: ${tokens.refresh_token ? "yes" : "no"}`);
245
+ }
246
+ async function logoutCommand() {
247
+ await clearTokens();
248
+ console.log("Tokens cleared. Run 'npx mcp-travelcode auth' to re-authenticate.");
249
+ }
250
+ // --- Entry point ---
251
+ const command = process.argv[2];
252
+ switch (command) {
253
+ case "auth":
254
+ case "login":
255
+ authCommand().catch((err) => {
256
+ console.error("Auth error:", err.message);
257
+ process.exit(1);
258
+ });
259
+ break;
260
+ case "status":
261
+ statusCommand().catch((err) => {
262
+ console.error("Error:", err.message);
263
+ process.exit(1);
264
+ });
265
+ break;
266
+ case "logout":
267
+ logoutCommand().catch((err) => {
268
+ console.error("Error:", err.message);
269
+ process.exit(1);
270
+ });
271
+ break;
272
+ default:
273
+ console.log("Usage: mcp-travelcode <command>");
274
+ console.log("");
275
+ console.log("Commands:");
276
+ console.log(" auth Authenticate with TravelCode (opens browser)");
277
+ console.log(" status Show current auth status");
278
+ console.log(" logout Clear saved tokens");
279
+ break;
280
+ }
281
+ //# sourceMappingURL=cli-auth.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Token storage for CLI-obtained OAuth tokens.
3
+ *
4
+ * Tokens are stored in ~/.travelcode/tokens.json.
5
+ * Format:
6
+ * {
7
+ * "access_token": "...",
8
+ * "refresh_token": "...",
9
+ * "expires_at": 1234567890,
10
+ * "scope": "flights:search airports:read ...",
11
+ * "client_id": "...",
12
+ * "issuer": "https://api.travel-code.com"
13
+ * }
14
+ */
15
+ export interface StoredTokens {
16
+ access_token: string;
17
+ refresh_token: string;
18
+ expires_at: number;
19
+ scope: string;
20
+ client_id: string;
21
+ issuer: string;
22
+ }
23
+ export interface StoredClient {
24
+ client_id: string;
25
+ client_name: string;
26
+ redirect_uris: string[];
27
+ issuer: string;
28
+ }
29
+ export declare function loadTokens(): Promise<StoredTokens | null>;
30
+ export declare function saveTokens(tokens: StoredTokens): Promise<void>;
31
+ export declare function clearTokens(): Promise<void>;
32
+ /**
33
+ * Returns a valid access token, refreshing if expired.
34
+ * Returns null if no tokens stored or refresh fails.
35
+ */
36
+ export declare function getValidToken(issuer: string): Promise<string | null>;
37
+ export declare function loadClient(issuer: string): Promise<StoredClient | null>;
38
+ export declare function saveClient(client: StoredClient): Promise<void>;
39
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Token storage for CLI-obtained OAuth tokens.
3
+ *
4
+ * Tokens are stored in ~/.travelcode/tokens.json.
5
+ * Format:
6
+ * {
7
+ * "access_token": "...",
8
+ * "refresh_token": "...",
9
+ * "expires_at": 1234567890,
10
+ * "scope": "flights:search airports:read ...",
11
+ * "client_id": "...",
12
+ * "issuer": "https://api.travel-code.com"
13
+ * }
14
+ */
15
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
16
+ import { join } from "node:path";
17
+ import { homedir } from "node:os";
18
+ const TOKEN_DIR = join(homedir(), ".travelcode");
19
+ const TOKEN_FILE = join(TOKEN_DIR, "tokens.json");
20
+ const CLIENT_FILE = join(TOKEN_DIR, "client.json");
21
+ async function ensureDir() {
22
+ await mkdir(TOKEN_DIR, { recursive: true });
23
+ }
24
+ // --- Tokens ---
25
+ export async function loadTokens() {
26
+ try {
27
+ const data = await readFile(TOKEN_FILE, "utf-8");
28
+ return JSON.parse(data);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ export async function saveTokens(tokens) {
35
+ await ensureDir();
36
+ await writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2), "utf-8");
37
+ }
38
+ export async function clearTokens() {
39
+ try {
40
+ const { unlink } = await import("node:fs/promises");
41
+ await unlink(TOKEN_FILE);
42
+ }
43
+ catch {
44
+ // ignore if file doesn't exist
45
+ }
46
+ }
47
+ /**
48
+ * Returns a valid access token, refreshing if expired.
49
+ * Returns null if no tokens stored or refresh fails.
50
+ */
51
+ export async function getValidToken(issuer) {
52
+ const tokens = await loadTokens();
53
+ if (!tokens)
54
+ return null;
55
+ if (tokens.issuer !== issuer)
56
+ return null;
57
+ const now = Math.floor(Date.now() / 1000);
58
+ // Token still valid (with 60s buffer)
59
+ if (tokens.expires_at > now + 60) {
60
+ return tokens.access_token;
61
+ }
62
+ // Try to refresh
63
+ if (!tokens.refresh_token || !tokens.client_id)
64
+ return null;
65
+ try {
66
+ const response = await fetch(`${issuer}/oauth/token`, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
69
+ body: new URLSearchParams({
70
+ grant_type: "refresh_token",
71
+ refresh_token: tokens.refresh_token,
72
+ client_id: tokens.client_id,
73
+ }),
74
+ });
75
+ if (!response.ok) {
76
+ console.error("Token refresh failed, please re-authenticate: npx mcp-travelcode auth");
77
+ return null;
78
+ }
79
+ const data = (await response.json());
80
+ const refreshedTokens = {
81
+ access_token: data.access_token,
82
+ refresh_token: data.refresh_token,
83
+ expires_at: now + data.expires_in,
84
+ scope: data.scope,
85
+ client_id: tokens.client_id,
86
+ issuer: tokens.issuer,
87
+ };
88
+ await saveTokens(refreshedTokens);
89
+ return refreshedTokens.access_token;
90
+ }
91
+ catch (error) {
92
+ console.error("Token refresh error:", error.message);
93
+ return null;
94
+ }
95
+ }
96
+ // --- Client registration ---
97
+ export async function loadClient(issuer) {
98
+ try {
99
+ const data = await readFile(CLIENT_FILE, "utf-8");
100
+ const client = JSON.parse(data);
101
+ if (client.issuer !== issuer)
102
+ return null;
103
+ return client;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ export async function saveClient(client) {
110
+ await ensureDir();
111
+ await writeFile(CLIENT_FILE, JSON.stringify(client, null, 2), "utf-8");
112
+ }
113
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1,42 @@
1
+ import { TravelCodeConfig } from "../config.js";
2
+ export declare class TravelCodeAuthError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare class TravelCodeNotFoundError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ export declare class TravelCodeValidationError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ export declare class TravelCodeServerError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ export declare class TravelCodeApiClient {
15
+ private baseUrl;
16
+ private token;
17
+ private issuer;
18
+ constructor(config: TravelCodeConfig);
19
+ /**
20
+ * Ensures the token is still valid, refreshing via OAuth if needed.
21
+ * Falls back to the current token if refresh is not available.
22
+ */
23
+ private ensureValidToken;
24
+ get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
25
+ post<T>(path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
26
+ getAerodatabox<T>(aerodataboxPath: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
27
+ /**
28
+ * POST request that returns an SSE stream.
29
+ * Collects events and returns them as an array of {event, data} objects.
30
+ */
31
+ postSSE(path: string, body: Record<string, unknown>, timeoutMs?: number): Promise<Array<{
32
+ event: string;
33
+ data: unknown;
34
+ }>>;
35
+ /**
36
+ * GET with accessToken as query parameter (used by hotel location endpoints).
37
+ */
38
+ getWithTokenParam<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
39
+ private headers;
40
+ private handleResponse;
41
+ }
42
+ //# sourceMappingURL=api-client.d.ts.map