#
tokens: 49914/50000 31/35 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/privilegemendes/amadeus-mcp-server-standalone?lines=true&page={x} to view the full context.

# Directory Structure

```
├── __tests__
│   ├── amadeus-mock.test.js
│   ├── basic.test.js
│   ├── cache-mock.test.js
│   ├── caching.test.ts
│   ├── integration
│   │   ├── airport-search.test.js
│   │   ├── flight-search.test.js
│   │   ├── price-analysis.test.js
│   │   └── setup.js
│   ├── mcp-inspector.test.js
│   ├── prompts.test.ts
│   └── tools.test.ts
├── .gitignore
├── api-spectifications
│   ├── AirportCitySearch_v1_Version_1.0_swagger_specification.json
│   ├── AirportNearestRelevant_v1_Version_1.0_swagger_specification.json
│   ├── AirportRoutes_v1_Version_1.1_swagger_specification.json
│   ├── FlightAvailabilitiesSearch_v1_Version_1.0_swagger_specification.json
│   ├── FlightCheapestDateSearch_v1_Version_1.0_swagger_specification-full.json
│   ├── FlightCheapestDateSearch_v1_Version_1.0_swagger_specification.json
│   └── FlightInspirationSearch_v1_Version_1.0_swagger_specification.json
├── biome.json
├── jest.config.js
├── package-lock.json
├── package.json
├── Procfile
├── railway.toml
├── README.md
├── scripts
│   ├── kill-ports.js
│   └── test-tools.js
├── src
│   ├── cli.ts
│   ├── index.ts
│   ├── naturalLanguageHandler.ts
│   ├── prompt.ts
│   ├── queryAnalyzer.ts
│   ├── queryProcessor.ts
│   ├── resources.ts
│   └── tools.ts
├── test-sse.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log
 4 | yarn-debug.log
 5 | yarn-error.log
 6 | .pnpm-debug.log
 7 | 
 8 | # Environment variables
 9 | .env
10 | .env
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 | 
15 | # Build output
16 | dist/
17 | build/
18 | out/
19 | *.tsbuildinfo
20 | 
21 | # Coverage directory used by tools like Jest
22 | coverage/
23 | 
24 | # Logs
25 | logs
26 | *.log
27 | 
28 | # IDE - VSCode
29 | .vscode/*
30 | !.vscode/settings.json
31 | !.vscode/tasks.json
32 | !.vscode/launch.json
33 | !.vscode/extensions.json
34 | 
35 | # JetBrains IDEs
36 | .idea/
37 | *.iml
38 | *.iws
39 | *.ipr
40 | .idea_modules/
41 | 
42 | # macOS
43 | .DS_Store
44 | .AppleDouble
45 | .LSOverride
46 | ._*
47 | 
48 | # Windows
49 | Thumbs.db
50 | ehthumbs.db
51 | Desktop.ini
52 | $RECYCLE.BIN/
53 | 
54 | # Testing
55 | *.spec.results
56 | 
57 | # Debug
58 | .node-debug/
59 | 
60 | # Temp files
61 | tmp/
62 | temp/
63 | .tmp/
64 | .temp/
65 | 
66 | # Cache
67 | .cache/
68 | .npm 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Amadeus MCP Server
  2 | 
  3 | This is a Model Context Protocol (MCP) server that connects to the Amadeus API to provide flight search, booking, and analysis capabilities for AI assistants.
  4 | 
  5 | ## Features
  6 | 
  7 | - **Flight Search**: Find flights between airports with various parameters
  8 | - **Airport Information**: Search for airports by keyword, city, or country
  9 | - **Price Analysis**: Get price metrics for routes to determine if current prices are high or low
 10 | - **Cheapest Dates**: Find the most economical dates to travel
 11 | - **Flight Details**: Get detailed information about specific flight offers
 12 | 
 13 | ## Prompts
 14 | 
 15 | The server provides several pre-configured prompts for common travel planning scenarios:
 16 | 
 17 | 1. **Analyze Flight Prices** (`analyze-flight-prices`): Analyze flight prices for a route with insights on pricing trends
 18 | 2. **Find Best Deals** (`find-best-deals`): Find the best flight deals for a specific route and date
 19 | 3. **Plan Multi-City Trip** (`plan-multi-city-trip`): Plan a complete multi-city itinerary with optimal routing
 20 | 4. **Find Cheapest Travel Dates** (`find-cheapest-travel-dates`): Identify the most economical dates to travel
 21 | 
 22 | ## Setup
 23 | 
 24 | ### Prerequisites
 25 | 
 26 | - Node.js 16.x or higher
 27 | - Amadeus API credentials (Client ID and Secret)
 28 | 
 29 | ### Installation
 30 | 
 31 | 1. Clone the repository:
 32 | ```
 33 | git clone https://github.com/yourusername/amadeus-mcp-server.git
 34 | cd amadeus-mcp-server
 35 | ```
 36 | 
 37 | 2. Install dependencies:
 38 | ```
 39 | npm install
 40 | ```
 41 | 
 42 | 3. Create a `.env` file in the root directory with your Amadeus API credentials:
 43 | ```
 44 | AMADEUS_CLIENT_ID=your_client_id
 45 | AMADEUS_CLIENT_SECRET=your_client_secret
 46 | ```
 47 | 
 48 | ### Running the Server
 49 | 
 50 | Build and start the server:
 51 | ```
 52 | npm run build
 53 | npm start
 54 | ```
 55 | 
 56 | For development:
 57 | ```
 58 | npm run dev
 59 | ```
 60 | 
 61 | ### Testing and Development
 62 | 
 63 | This project uses Jest for testing and Biome for linting and formatting.
 64 | 
 65 | Run unit tests:
 66 | ```
 67 | npx jest
 68 | ```
 69 | 
 70 | Run tests with watch mode:
 71 | ```
 72 | npx jest --watch
 73 | ```
 74 | 
 75 | Run tests with coverage:
 76 | ```
 77 | npx jest --coverage
 78 | ```
 79 | 
 80 | Run integration tests (requires Amadeus API credentials):
 81 | ```
 82 | npm run test:integration
 83 | ```
 84 | 
 85 | Run linting:
 86 | ```
 87 | npm run lint
 88 | ```
 89 | 
 90 | Format code:
 91 | ```
 92 | npm run format
 93 | ```
 94 | 
 95 | ## Integration Testing
 96 | 
 97 | The project includes comprehensive integration tests that verify the server's interaction with the real Amadeus API. These tests help ensure that our API clients work correctly with the actual API endpoints and handle responses appropriately.
 98 | 
 99 | ### Requirements for Integration Tests
100 | 
101 | - **Amadeus API Credentials**: Tests require valid Amadeus API credentials in the `.env` file:
102 |   ```
103 |   AMADEUS_CLIENT_ID=your_client_id
104 |   AMADEUS_CLIENT_SECRET=your_client_secret
105 |   ```
106 | 
107 | - **Test Environment**: Tests are configured to use the Amadeus Test Environment, not the production API.
108 | 
109 | ### Running Integration Tests
110 | 
111 | ```
112 | npm run test:integration
113 | ```
114 | 
115 | The integration tests are located in `__tests__/integration` and validate the following API features:
116 | 
117 | - **Airport Search**: Searching for airports by code or keyword
118 | - **Flight Search**: Finding flights for one-way and round-trip journeys
119 | - **Price Analysis**: Getting price metrics for specific routes
120 | 
121 | ### Best Practices for Integration Testing
122 | 
123 | 1. **API Rate Limits**: The tests include automatic rate limit handling with exponential backoff to avoid API throttling. When running tests frequently, you may still encounter rate limits.
124 | 
125 | 2. **Conditional Testing**: Tests are designed to skip automatically if API credentials are missing, allowing the test suite to run without errors in environments without credentials.
126 | 
127 | 3. **Test in Isolation**: When developing a new feature, you can run specific test files:
128 |    ```
129 |    npx jest __tests__/integration/flight-search.test.js
130 |    ```
131 | 
132 | 4. **Longer Timeouts**: Integration tests use longer timeouts (60 seconds) to accommodate network latency and retries.
133 | 
134 | 5. **Mock for CI/CD**: For continuous integration pipelines where real API access isn't available, use `__tests__/amadeus-mock.test.js` which runs without actual API calls.
135 | 
136 | ## Integration
137 | 
138 | To use this MCP server with OpenAI's Assistant API or other compatible AI systems, configure the assistant to connect to this server's endpoint.
139 | 
140 | ## Tools
141 | 
142 | The server provides the following tools:
143 | 
144 | ### `search-flights`
145 | Search for flight offers between two locations.
146 | 
147 | ### `search-airports`
148 | Search for airports by keyword, city name, or IATA code.
149 | 
150 | ### `flight-price-analysis`
151 | Get price metrics for a flight route to determine if current prices are high or low.
152 | 
153 | ### `get-flight-details`
154 | Get detailed information about a specific flight offer.
155 | 
156 | ### `find-cheapest-dates`
157 | Find the cheapest dates to fly for a given route.
158 | 
159 | ## Resources
160 | 
161 | The server provides schema resources for:
162 | 
163 | - Flight offers (`schema://flight-offers`)
164 | - Airports (`schema://airports`)
165 | 
166 | ## License
167 | 
168 | MIT 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
1 | /** @type {import('jest').Config} */
2 | module.exports = {
3 |   testEnvironment: 'node',
4 |   testMatch: ['**/__tests__/**/*.test.js'],
5 | }; 
```

--------------------------------------------------------------------------------
/railway.toml:
--------------------------------------------------------------------------------

```toml
 1 | [build]
 2 | builder = "nixpacks"
 3 | buildCommand = "npm install && npm run build"
 4 | 
 5 | [deploy]
 6 | startCommand = "node dist/cli.js"
 7 | healthcheckPath = "/health"
 8 | healthcheckTimeout = 100
 9 | restartPolicyType = "on_failure"
10 | 
11 | [env]
12 | NODE_ENV = "production" 
```

--------------------------------------------------------------------------------
/__tests__/basic.test.js:
--------------------------------------------------------------------------------

```javascript
 1 | // No imports needed for basic tests
 2 | 
 3 | describe('Basic syntax tests', () => {
 4 |   test('JavaScript syntax test', () => {
 5 |     // Test basic JavaScript features
 6 |     const array = [1, 2, 3];
 7 |     expect(array.length).toBe(3);
 8 |     
 9 |     const obj = { name: 'Test', value: 42 };
10 |     expect(obj.name).toBe('Test');
11 |     expect(obj.value).toBe(42);
12 |   });
13 |   
14 |   test('Async/await support', async () => {
15 |     const result = await Promise.resolve('async works');
16 |     expect(result).toBe('async works');
17 |   });
18 |   
19 |   test('Arrow functions', () => {
20 |     const add = (a, b) => a + b;
21 |     expect(add(2, 3)).toBe(5);
22 |   });
23 | }); 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "strict": false,
 8 |     "noImplicitAny": false,
 9 |     "strictNullChecks": false,
10 |     "noUnusedLocals": false,
11 |     "noUnusedParameters": false,
12 |     "noImplicitReturns": false,
13 |     "skipLibCheck": true,
14 |     "outDir": "./dist",
15 |     "declaration": true,
16 |     "sourceMap": true,
17 |     "allowJs": true,
18 |     "resolveJsonModule": true,
19 |     "baseUrl": ".",
20 |     "paths": {
21 |       "*": ["node_modules/*"]
22 |     }
23 |   },
24 |   "include": ["src/**/*"],
25 |   "exclude": ["node_modules", "dist"]
26 | }
```

--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 | 	"$schema": "https://biomejs.dev/schemas/1.7.2/schema.json",
 3 | 	"organizeImports": { "enabled": true },
 4 | 	"linter": {
 5 | 		"enabled": true,
 6 | 		"rules": {
 7 | 			"recommended": true,
 8 | 			"style": {
 9 | 				"noParameterAssign": "warn",
10 | 				"noNonNullAssertion": "warn"
11 | 			},
12 | 			"correctness": {
13 | 				"useExhaustiveDependencies": "warn"
14 | 			},
15 | 			"suspicious": {
16 | 				"noExplicitAny": "warn"
17 | 			},
18 | 			"a11y": {
19 | 				"useHtmlLang": "warn"
20 | 			}
21 | 		}
22 | 	},
23 | 	"javascript": {
24 | 		"formatter": {
25 | 			"quoteStyle": "single",
26 | 			"indentStyle": "space"
27 | 		}
28 | 	},
29 | 	"files": {
30 | 		"ignore": [
31 | 			".next/*",
32 | 			"coverage/*",
33 | 			"node_modules/*",
34 | 			"package.json",
35 | 			"package-lock.json",
36 | 			"tsconfig.json",
37 | 			"renovate.json"
38 | 		]
39 | 	}
40 | }
41 | 
```

--------------------------------------------------------------------------------
/scripts/kill-ports.js:
--------------------------------------------------------------------------------

```javascript
 1 | #!/usr/bin/env node
 2 | 
 3 | const { exec } = require('node:child_process');
 4 | const { promisify } = require('node:util');
 5 | const execAsync = promisify(exec);
 6 | 
 7 | // Common ports that might be used by the MCP server
 8 | const ports = [3000, 3001, 3002, 8080, 8081, 8082, 5173];
 9 | 
10 | async function killPort(port) {
11 |   try {
12 |     // For macOS/Linux
13 |     const { stdout } = await execAsync(`lsof -i :${port} -t`);
14 |     if (stdout.trim()) {
15 |       const pid = stdout.trim();
16 |       await execAsync(`kill -9 ${pid}`);
17 |       console.log(`Killed process on port ${port} (PID: ${pid})`);
18 |     } else {
19 |       console.log(`No process found on port ${port}`);
20 |     }
21 |   } catch (error) {
22 |     if (error.message.includes('No such process')) {
23 |       console.log(`No process found on port ${port}`);
24 |     } else {
25 |       console.error(`Error checking port ${port}:`, error.message);
26 |     }
27 |   }
28 | }
29 | 
30 | async function killAllPorts() {
31 |   console.log('Checking and killing processes on common ports...');
32 |   
33 |   for (const port of ports) {
34 |     await killPort(port);
35 |   }
36 |   
37 |   console.log('\nDone! All common ports have been checked.');
38 | }
39 | 
40 | // Run the script
41 | killAllPorts().catch(console.error); 
```

--------------------------------------------------------------------------------
/test-sse.js:
--------------------------------------------------------------------------------

```javascript
 1 | import EventSource from 'eventsource';
 2 | import fetch from 'node-fetch';
 3 | 
 4 | const BASE_URL = 'https://amadeus-mcp-server-production-5e4a.up.railway.app';
 5 | 
 6 | // Connect to SSE endpoint
 7 | const sse = new EventSource(`${BASE_URL}/sse`);
 8 | let connectionId = null;
 9 | 
10 | // Listen for the initial connection message
11 | sse.onmessage = async (event) => {
12 |     const data = JSON.parse(event.data);
13 |     if (data.connectionId) {
14 |         connectionId = data.connectionId;
15 |         console.log('Connected with ID:', connectionId);
16 |         
17 |         // Once we have the connection ID, send a test message
18 |         try {
19 |             const response = await fetch(`${BASE_URL}/messages?connectionId=${connectionId}`, {
20 |                 method: 'POST',
21 |                 headers: {
22 |                     'Content-Type': 'application/json'
23 |                 },
24 |                 body: JSON.stringify({
25 |                     type: 'test',
26 |                     payload: { message: 'Hello from test client!' }
27 |                 })
28 |             });
29 |             
30 |             const result = await response.json();
31 |             console.log('Message sent response:', result);
32 |         } catch (error) {
33 |             console.error('Error sending message:', error);
34 |         }
35 |     } else {
36 |         console.log('Received message:', data);
37 |     }
38 | };
39 | 
40 | sse.onerror = (error) => {
41 |     console.error('SSE Error:', error);
42 | };
43 | 
44 | // Keep the script running
45 | process.on('SIGINT', () => {
46 |     sse.close();
47 |     process.exit();
48 | }); 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@privilegemendes/amadeus-mcp-server",
 3 |   "version": "1.0.4",
 4 |   "description": "MCP server for Amadeus flight search and booking",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "types": "dist/index.d.ts",
 8 |   "bin": {
 9 |     "amadeus-mcp-server": "./dist/cli.js"
10 |   },
11 |   "files": [
12 |     "dist",
13 |     "README.md",
14 |     "LICENSE"
15 |   ],
16 |   "scripts": {
17 |     "clean": "rimraf dist",
18 |     "build": "npm run clean && tsc && chmod +x dist/cli.js",
19 |     "start": "node dist/cli.js",
20 |     "dev": "ts-node src/index.ts",
21 |     "lint": "biome check src/",
22 |     "format": "biome format --write src/",
23 |     "test": "jest",
24 |     "test:watch": "jest --watch",
25 |     "test:coverage": "jest --coverage",
26 |     "test:integration": "jest __tests__/integration",
27 |     "test:inspector": "node __tests__/mcp-inspector.test.js",
28 |     "mcp:inspect": "npx @modelcontextprotocol/inspector node dist/cli.js",
29 |     "mcp:test": "node scripts/test-tools.js",
30 |     "kill-ports": "node scripts/kill-ports.js",
31 |     "prepublishOnly": "npm run build",
32 |     "publish": "npm publish"
33 |   },
34 |   "keywords": [
35 |     "mcp",
36 |     "amadeus",
37 |     "flight-search",
38 |     "model-context-protocol",
39 |     "ai"
40 |   ],
41 |   "author": "Privilege Mendes",
42 |   "license": "MIT",
43 |   "dependencies": {
44 |     "@modelcontextprotocol/sdk": "^1.7.0",
45 |     "amadeus": "^7.1.0",
46 |     "dotenv": "^16.0.3",
47 |     "express": "^4.18.2",
48 |     "node-cache": "^5.1.2",
49 |     "zod": "^3.20.6"
50 |   },
51 |   "devDependencies": {
52 |     "@biomejs/biome": "1.7.2",
53 |     "@types/express": "^4.17.21",
54 |     "@types/jest": "^29.5.11",
55 |     "@types/node": "^18.14.0",
56 |     "eventsource": "^3.0.5",
57 |     "jest": "^29.7.0",
58 |     "node-fetch": "^3.3.2",
59 |     "rimraf": "^6.0.1",
60 |     "ts-jest": "^29.1.2",
61 |     "ts-node": "^10.9.1",
62 |     "typescript": "^4.9.5"
63 |   },
64 |   "engines": {
65 |     "node": ">=16.0.0"
66 |   },
67 |   "repository": {
68 |     "type": "git",
69 |     "url": "https://github.com/privilegemendes/amadeus-mcp-server-standalone.git"
70 |   },
71 |   "bugs": {
72 |     "url": "https://github.com/privilegemendes/amadeus-mcp-server-standalone/issues"
73 |   },
74 |   "homepage": "https://github.com/privilegemendes/amadeus-mcp-server-standalone#readme",
75 |   "publishConfig": {
76 |     "access": "public"
77 |   }
78 | }
79 | 
```

--------------------------------------------------------------------------------
/__tests__/caching.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { cache, cachedApiCall } from '../src/index';
 2 | 
 3 | // Mock console.error to avoid cluttering test output
 4 | console.error = jest.fn();
 5 | 
 6 | describe('Caching Functionality', () => {
 7 |   beforeEach(() => {
 8 |     // Clear the cache and console.error mocks before each test
 9 |     jest.clearAllMocks();
10 | 
11 |     // Get all cache keys and delete them
12 |     const keys = Object.keys(
13 |       (cache as { data: Record<string, unknown> }).data || {},
14 |     );
15 |     for (const key of keys) {
16 |       cache.del(key);
17 |     }
18 |   });
19 | 
20 |   test('cachedApiCall should call the API when cache miss', async () => {
21 |     // Mock API call
22 |     const mockApiCall = jest.fn().mockResolvedValue({ data: 'test data' });
23 | 
24 |     // Call the function
25 |     const result = await cachedApiCall('test-key', 60, mockApiCall);
26 | 
27 |     // Assertions
28 |     expect(mockApiCall).toHaveBeenCalledTimes(1);
29 |     expect(result).toEqual({ data: 'test data' });
30 |     expect(console.error).toHaveBeenCalledWith(
31 |       'Cache miss for test-key, calling API...',
32 |     );
33 |   });
34 | 
35 |   test('cachedApiCall should return cached data on cache hit', async () => {
36 |     // Prepare cache
37 |     const testData = { data: 'cached data' };
38 |     cache.set('test-key-2', testData);
39 | 
40 |     // Mock API call - this should not be called
41 |     const mockApiCall = jest.fn();
42 | 
43 |     // Call the function
44 |     const result = await cachedApiCall('test-key-2', 60, mockApiCall);
45 | 
46 |     // Assertions
47 |     expect(mockApiCall).not.toHaveBeenCalled();
48 |     expect(result).toEqual(testData);
49 |     expect(console.error).toHaveBeenCalledWith('Cache hit for test-key-2');
50 |   });
51 | 
52 |   test('cachedApiCall should handle API errors', async () => {
53 |     // Mock API call that throws
54 |     const mockError = new Error('API Error');
55 |     const mockApiCall = jest.fn().mockRejectedValue(mockError);
56 | 
57 |     // Call the function and expect it to throw
58 |     await expect(cachedApiCall('test-key-3', 60, mockApiCall)).rejects.toThrow(
59 |       'API Error',
60 |     );
61 | 
62 |     // Assertions
63 |     expect(mockApiCall).toHaveBeenCalledTimes(1);
64 |     expect(console.error).toHaveBeenCalledWith(
65 |       'Cache miss for test-key-3, calling API...',
66 |     );
67 |     expect(console.error).toHaveBeenCalledWith(
68 |       'API call failed for test-key-3:',
69 |       mockError,
70 |     );
71 |   });
72 | });
73 | 
```

--------------------------------------------------------------------------------
/__tests__/mcp-inspector.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { spawn } = require('node:child_process');
  2 | const { setTimeout } = require('node:timers/promises');
  3 | 
  4 | // Helper function to get a future date string
  5 | const getFutureDate = (daysFromNow) => {
  6 |   const date = new Date();
  7 |   date.setDate(date.getDate() + daysFromNow);
  8 |   return date.toISOString().split('T')[0]; // YYYY-MM-DD format
  9 | };
 10 | 
 11 | // Test cases for different tools
 12 | const testCases = [
 13 |   {
 14 |     tool: 'search-flights',
 15 |     params: {
 16 |       originLocationCode: 'JFK',
 17 |       destinationLocationCode: 'LAX',
 18 |       departureDate: getFutureDate(90),
 19 |       adults: 1,
 20 |       children: 0,
 21 |       infants: 0,
 22 |       nonStop: false,
 23 |       currencyCode: 'USD',
 24 |       maxResults: 5
 25 |     }
 26 |   },
 27 |   {
 28 |     tool: 'search-airports',
 29 |     params: {
 30 |       keyword: 'JFK',
 31 |       maxResults: 5
 32 |     }
 33 |   },
 34 |   {
 35 |     tool: 'flight-price-analysis',
 36 |     params: {
 37 |       originLocationCode: 'JFK',
 38 |       destinationLocationCode: 'LAX',
 39 |       departureDate: getFutureDate(90),
 40 |       currencyCode: 'USD'
 41 |     }
 42 |   },
 43 |   {
 44 |     tool: 'find-cheapest-dates',
 45 |     params: {
 46 |       originLocationCode: 'JFK',
 47 |       destinationLocationCode: 'LAX',
 48 |       departureDate: getFutureDate(90),
 49 |       oneWay: true,
 50 |       nonStop: false,
 51 |       currencyCode: 'USD'
 52 |     }
 53 |   }
 54 | ];
 55 | 
 56 | async function runInspectorTests() {
 57 |   // Start the MCP server
 58 |   const server = spawn('node', ['dist/cli.js']);
 59 |   
 60 |   // Wait for server to start
 61 |   await setTimeout(2000);
 62 | 
 63 |   // Process server output
 64 |   server.stdout.on('data', (data) => {
 65 |     console.log(`Server: ${data}`);
 66 |   });
 67 | 
 68 |   server.stderr.on('data', (data) => {
 69 |     console.error(`Server Error: ${data}`);
 70 |   });
 71 | 
 72 |   // Run each test case
 73 |   for (const testCase of testCases) {
 74 |     console.log(`\nTesting ${testCase.tool}...`);
 75 |     
 76 |     // Create the inspector command
 77 |     const inspector = spawn('npx', [
 78 |       '@modelcontextprotocol/inspector',
 79 |       'node',
 80 |       'dist/cli.js',
 81 |       '--tool',
 82 |       testCase.tool,
 83 |       '--params',
 84 |       JSON.stringify(testCase.params)
 85 |     ]);
 86 | 
 87 |     // Process inspector output
 88 |     inspector.stdout.on('data', (data) => {
 89 |       console.log(`Inspector: ${data}`);
 90 |     });
 91 | 
 92 |     inspector.stderr.on('data', (data) => {
 93 |       console.error(`Inspector Error: ${data}`);
 94 |     });
 95 | 
 96 |     // Wait for inspector to complete
 97 |     await new Promise((resolve) => {
 98 |       inspector.on('close', (code) => {
 99 |         console.log(`Inspector exited with code ${code}`);
100 |         resolve();
101 |       });
102 |     });
103 | 
104 |     // Wait between tests to avoid rate limiting
105 |     await setTimeout(1000);
106 |   }
107 | 
108 |   // Cleanup
109 |   server.kill();
110 | }
111 | 
112 | // Run the tests
113 | runInspectorTests().catch(console.error); 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 | const Amadeus = require('amadeus');
 4 | import dotenv from 'dotenv';
 5 | // Use require instead of import for node-cache
 6 | const NodeCache = require('node-cache');
 7 | 
 8 | // Define a type for our cache to make TypeScript happy
 9 | type TypedCache = {
10 |   get: <T>(key: string) => T | undefined;
11 |   set: <T>(key: string, value: T, ttl?: number) => boolean;
12 | };
13 | 
14 | // Load environment variables
15 | dotenv.config();
16 | 
17 | // Initialize Amadeus client only if credentials are available
18 | export let amadeus = null;
19 | if (process.env.AMADEUS_CLIENT_ID && process.env.AMADEUS_CLIENT_SECRET) {
20 |   amadeus = new Amadeus({
21 |     clientId: process.env.AMADEUS_CLIENT_ID,
22 |     clientSecret: process.env.AMADEUS_CLIENT_SECRET,
23 |   });
24 | } else {
25 |   console.error('Warning: Amadeus credentials not found in environment variables');
26 | }
27 | 
28 | // Create MCP server - FIXED VERSION
29 | export const server = new McpServer({
30 |   name: 'amadeus-mcp-server',
31 |   version: '1.0.0'
32 | });
33 | 
34 | // Create a cache instance
35 | // Default TTL is 10 minutes (600 seconds)
36 | export const cache = new NodeCache({
37 |   stdTTL: 600,
38 |   checkperiod: 120,
39 |   useClones: false,
40 | }) as TypedCache;
41 | 
42 | /**
43 |  * Wrapper for Amadeus API calls with caching
44 |  * @param cacheKey - Key for caching
45 |  * @param ttl - Time to live in seconds
46 |  * @param apiCall - Function that returns a promise with the API call
47 |  * @returns Promise with API response
48 |  */
49 | export async function cachedApiCall<T>(
50 |   cacheKey: string,
51 |   ttl: number,
52 |   apiCall: () => Promise<T>,
53 | ): Promise<T> {
54 |   // Check if we have a cached response
55 |   const cachedResponse = cache.get<T>(cacheKey);
56 |   if (cachedResponse) {
57 |     console.error(`Cache hit for ${cacheKey}`);
58 |     return cachedResponse;
59 |   }
60 | 
61 |   // If not cached, make the API call
62 |   console.error(`Cache miss for ${cacheKey}, calling API...`);
63 |   try {
64 |     const response = await apiCall();
65 | 
66 |     // Cache the response with the specified TTL
67 |     cache.set<T>(cacheKey, response, ttl);
68 | 
69 |     return response;
70 |   } catch (error: unknown) {
71 |     console.error(`API call failed for ${cacheKey}:`, error);
72 |     throw error;
73 |   }
74 | }
75 | 
76 | // Start the server
77 | export async function main() {
78 |   // Import all components to register tools, resources, and prompts
79 |   // This ensures they are properly registered with the server
80 |   await Promise.all([
81 |     import('./tools.js'),
82 |     import('./resources.js'),
83 |     import('./prompt.js')
84 |   ]);
85 | 
86 |   // Start server
87 |   console.error('Starting Amadeus Flight MCP Server...');
88 |   const transport = new StdioServerTransport();
89 |   await server.connect(transport);
90 |   console.error('Amadeus Flight MCP Server running');
91 | }
92 | 
93 | // Only run main if this file is being run directly
94 | if (require.main === module) {
95 |   main().catch((error: unknown) => {
96 |     console.error('Fatal error:', error);
97 |     process.exit(1);
98 |   });
99 | }
```

--------------------------------------------------------------------------------
/__tests__/integration/airport-search.test.js:
--------------------------------------------------------------------------------

```javascript
 1 | /**
 2 |  * Integration tests for Airport Search API
 3 |  */
 4 | 
 5 | const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
 6 | 
 7 | // Only run these tests if Amadeus credentials are available
 8 | describe('Airport Search API - Integration', () => {
 9 |   // Set longer timeout for API calls (60 seconds to account for retries)
10 |   jest.setTimeout(60000);
11 | 
12 |   conditionalTest(test, 'should find JFK airport', async () => {
13 |     // Skip if credentials not available (handled by conditionalTest)
14 |     expect(amadeus).not.toBeNull();
15 | 
16 |     // Parameters for the search
17 |     const params = {
18 |       keyword: 'JFK',
19 |       subType: 'AIRPORT'
20 |       // Removed max parameter as it's causing issues with the API
21 |     };
22 | 
23 |     try {
24 |       // Use the makeApiCallWithRetry helper to handle rate limiting
25 |       const response = await makeApiCallWithRetry(() => 
26 |         amadeus.referenceData.locations.get(params)
27 |       );
28 |       
29 |       // Basic validation
30 |       expect(response).toBeDefined();
31 |       expect(response.data).toBeDefined();
32 |       expect(Array.isArray(response.data)).toBe(true);
33 |       
34 |       // If we searched for JFK, we should find it
35 |       const jfkAirport = response.data.find(item => item.iataCode === 'JFK');
36 |       expect(jfkAirport).toBeDefined();
37 |       // Match the actual format returned by the API (uppercase)
38 |       expect(jfkAirport.name).toContain('KENNEDY');
39 |       expect(jfkAirport.subType).toBe('AIRPORT');
40 |       
41 |       console.log('Found JFK airport:', jfkAirport.name);
42 |     } catch (error) {
43 |       // Important to see the actual error if the API call fails
44 |       console.error('API Error:', error);
45 |       
46 |       // Rethrow to fail the test
47 |       throw error;
48 |     }
49 |   });
50 | 
51 |   conditionalTest(test, 'should find airports in New York', async () => {
52 |     expect(amadeus).not.toBeNull();
53 | 
54 |     const params = {
55 |       keyword: 'New York',
56 |       subType: 'AIRPORT'
57 |       // Removed max parameter as it's causing issues with the API
58 |     };
59 | 
60 |     try {
61 |       // Use the makeApiCallWithRetry helper to handle rate limiting
62 |       const response = await makeApiCallWithRetry(() => 
63 |         amadeus.referenceData.locations.get(params)
64 |       );
65 |       
66 |       expect(response).toBeDefined();
67 |       expect(response.data).toBeDefined();
68 |       expect(Array.isArray(response.data)).toBe(true);
69 |       expect(response.data.length).toBeGreaterThan(0);
70 |       
71 |       // Should find some New York airports
72 |       const nyAirports = response.data.filter(item => 
73 |         item.address && 
74 |         (item.address.cityName === 'NEW YORK' || 
75 |          item.name.includes('NEW YORK') || 
76 |          item.name.includes('JFK') || 
77 |          item.name.includes('LAGUARDIA'))
78 |       );
79 |       
80 |       expect(nyAirports.length).toBeGreaterThan(0);
81 |       console.log(`Found ${nyAirports.length} airports in New York:`, 
82 |         nyAirports.map(a => a.name).join(', '));
83 |     } catch (error) {
84 |       console.error('API Error:', error);
85 |       throw error;
86 |     }
87 |   });
88 | }); 
```

--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import express from 'express';
  3 | import cors from 'cors';
  4 | import dotenv from 'dotenv';
  5 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
  6 | import { server, amadeus } from './index.js';
  7 | 
  8 | // Load environment variables
  9 | dotenv.config();
 10 | 
 11 | // Start the server
 12 | async function main() {
 13 |   // Check for Amadeus credentials
 14 |   if (!amadeus) {
 15 |     console.error('Error: Amadeus API client could not be initialized. Check your environment variables.');
 16 |     process.exit(1);
 17 |   }
 18 | 
 19 |   // Set up Express app
 20 |   const app = express();
 21 |   
 22 |   // Configure CORS
 23 |   const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
 24 |   app.use(cors({
 25 |     origin: (origin, callback) => {
 26 |       // Allow requests with no origin (like mobile apps, curl, etc)
 27 |       if (!origin) return callback(null, true);
 28 |       
 29 |       if (allowedOrigins.indexOf(origin) === -1) {
 30 |         const msg = `The CORS policy for this site does not allow access from ${origin}`;
 31 |         return callback(new Error(msg), false);
 32 |       }
 33 |       
 34 |       return callback(null, true);
 35 |     }
 36 |   }));
 37 |   
 38 |   app.use(express.json());
 39 | 
 40 |   const PORT = process.env.PORT || 3000;
 41 |   
 42 |   // Store active transports
 43 |   const activeTransports = new Map();
 44 | 
 45 |   // SSE endpoint
 46 |   app.get('/sse', async (req, res) => {
 47 |     console.error('New SSE connection requested');
 48 |     
 49 |     // Generate a unique ID for this connection
 50 |     const connectionId = Date.now().toString();
 51 |     
 52 |     const transport = new SSEServerTransport('/messages', res);
 53 |     activeTransports.set(connectionId, transport);
 54 |     
 55 |     res.set({
 56 |       'Content-Type': 'text/event-stream',
 57 |       'Cache-Control': 'no-cache',
 58 |       Connection: 'keep-alive'
 59 |     });
 60 |     
 61 |     res.flushHeaders();
 62 |     
 63 |     // Send the connection ID to the client
 64 |     res.write(`data: ${JSON.stringify({ connectionId })}\n\n`);
 65 |     
 66 |     await server.connect(transport);
 67 |     
 68 |     req.on('close', () => {
 69 |       console.error(`SSE connection ${connectionId} closed`);
 70 |       activeTransports.delete(connectionId);
 71 |     });
 72 |   });
 73 | 
 74 |   // Handle client-to-server messages
 75 |   app.post('/messages', async (req, res) => {
 76 |     const connectionId = req.query.connectionId as string;
 77 |     const transport = activeTransports.get(connectionId);
 78 |     
 79 |     if (!transport) {
 80 |       return res.status(404).json({ error: 'Connection not found' });
 81 |     }
 82 |     
 83 |     await transport.handlePostMessage(req, res);
 84 |   });
 85 | 
 86 |   // Status endpoint
 87 |   app.get('/health', (req, res) => {
 88 |     res.json({ 
 89 |       status: 'ok',
 90 |       connections: activeTransports.size,
 91 |       version: process.env.npm_package_version || '1.0.0'
 92 |     });
 93 |   });
 94 | 
 95 |   // Start server
 96 |   app.listen(PORT, () => {
 97 |     console.error(`Amadeus Flight MCP Server running on port ${PORT}`);
 98 |     console.error(`Environment: ${process.env.NODE_ENV || 'development'}`);
 99 |     console.error(`Amadeus API client initialized: ${!!amadeus}`);
100 |   });
101 | }
102 | 
103 | main().catch((error: unknown) => {
104 |   console.error('Fatal error:', error);
105 |   process.exit(1);
106 | });
```

--------------------------------------------------------------------------------
/__tests__/cache-mock.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | // Mock for node-cache
  2 | class MockNodeCache {
  3 |   constructor() {
  4 |     this.data = {};
  5 |   }
  6 |   
  7 |   get(key) {
  8 |     return this.data[key];
  9 |   }
 10 |   
 11 |   set(key, value) {
 12 |     this.data[key] = value;
 13 |     return true;
 14 |   }
 15 |   
 16 |   del(key) {
 17 |     if (this.data[key]) {
 18 |       delete this.data[key];
 19 |       return 1;
 20 |     }
 21 |     return 0;
 22 |   }
 23 | }
 24 | 
 25 | // Mock for cachedApiCall function
 26 | describe('Cached API Call', () => {
 27 |   let mockCache;
 28 |   let cachedApiCall;
 29 |   
 30 |   beforeEach(() => {
 31 |     // Create a fresh mock cache for each test
 32 |     mockCache = new MockNodeCache();
 33 |     
 34 |     // Mock console.error
 35 |     console.error = jest.fn();
 36 |     
 37 |     // Implementation of cachedApiCall using our mock
 38 |     cachedApiCall = async (cacheKey, ttl, apiCall) => {
 39 |       // Check if we have a cached response
 40 |       const cachedResponse = mockCache.get(cacheKey);
 41 |       if (cachedResponse) {
 42 |         console.error(`Cache hit for ${cacheKey}`);
 43 |         return cachedResponse;
 44 |       }
 45 |     
 46 |       // If not cached, make the API call
 47 |       console.error(`Cache miss for ${cacheKey}, calling API...`);
 48 |       try {
 49 |         const response = await apiCall();
 50 |     
 51 |         // Cache the response with the specified TTL
 52 |         mockCache.set(cacheKey, response);
 53 |     
 54 |         return response;
 55 |       } catch (error) {
 56 |         console.error(`API call failed for ${cacheKey}:`, error);
 57 |         throw error;
 58 |       }
 59 |     };
 60 |   });
 61 |   
 62 |   test('should call the API when cache misses', async () => {
 63 |     // Mock API call
 64 |     const mockApiCall = jest.fn().mockResolvedValue({ data: 'test data' });
 65 |     
 66 |     // Call the function
 67 |     const result = await cachedApiCall('test-key', 60, mockApiCall);
 68 |     
 69 |     // Assertions
 70 |     expect(mockApiCall).toHaveBeenCalledTimes(1);
 71 |     expect(result).toEqual({ data: 'test data' });
 72 |     expect(console.error).toHaveBeenCalledWith('Cache miss for test-key, calling API...');
 73 |   });
 74 | 
 75 |   test('should return cached data on cache hit', async () => {
 76 |     // Prepare cache
 77 |     const testData = { data: 'cached data' };
 78 |     mockCache.set('test-key-2', testData);
 79 |     
 80 |     // Mock API call - this should not be called
 81 |     const mockApiCall = jest.fn();
 82 |     
 83 |     // Call the function
 84 |     const result = await cachedApiCall('test-key-2', 60, mockApiCall);
 85 |     
 86 |     // Assertions
 87 |     expect(mockApiCall).not.toHaveBeenCalled();
 88 |     expect(result).toEqual(testData);
 89 |     expect(console.error).toHaveBeenCalledWith('Cache hit for test-key-2');
 90 |   });
 91 | 
 92 |   test('should handle API errors', async () => {
 93 |     // Mock API call that throws
 94 |     const mockError = new Error('API Error');
 95 |     const mockApiCall = jest.fn().mockRejectedValue(mockError);
 96 |     
 97 |     // Call the function and expect it to throw
 98 |     await expect(cachedApiCall('test-key-3', 60, mockApiCall)).rejects.toThrow('API Error');
 99 |     
100 |     // Assertions
101 |     expect(mockApiCall).toHaveBeenCalledTimes(1);
102 |     expect(console.error).toHaveBeenCalledWith('Cache miss for test-key-3, calling API...');
103 |     expect(console.error).toHaveBeenCalledWith('API call failed for test-key-3:', mockError);
104 |   });
105 | }); 
```

--------------------------------------------------------------------------------
/__tests__/integration/setup.js:
--------------------------------------------------------------------------------

```javascript
 1 | /**
 2 |  * Integration test setup for Amadeus API
 3 |  * 
 4 |  * This module checks for the presence of Amadeus API credentials and initializes
 5 |  * the API client if they are available. Tests can use the shouldRunIntegrationTests
 6 |  * flag to determine if they should run or be skipped.
 7 |  */
 8 | 
 9 | // Load environment variables
10 | require('dotenv').config();
11 | 
12 | // Initialize Amadeus client if credentials are available
13 | const Amadeus = require('amadeus');
14 | let amadeus = null;
15 | let shouldRunIntegrationTests = false;
16 | 
17 | // Default delay between API calls to avoid rate limiting (in ms)
18 | const API_CALL_DELAY = 1000;
19 | // Maximum number of retries for rate limited requests
20 | const MAX_RETRIES = 3;
21 | 
22 | /**
23 |  * Sleep utility function
24 |  * @param {number} ms - Milliseconds to sleep
25 |  * @returns {Promise} - Promise that resolves after the specified time
26 |  */
27 | function sleep(ms) {
28 |   return new Promise(resolve => setTimeout(resolve, ms));
29 | }
30 | 
31 | /**
32 |  * Make an API call with retry logic for rate limiting
33 |  * @param {Function} apiCallFn - Function that makes the API call
34 |  * @param {number} retries - Number of retries left
35 |  * @param {number} delay - Delay between retries in ms
36 |  * @returns {Promise} - Promise that resolves with the API response
37 |  */
38 | async function makeApiCallWithRetry(apiCallFn, retries = MAX_RETRIES, delay = API_CALL_DELAY) {
39 |   try {
40 |     // Add a small delay before each API call to help avoid rate limiting
41 |     await sleep(delay);
42 |     return await apiCallFn();
43 |   } catch (error) {
44 |     // Check if it's a rate limiting error (HTTP 429)
45 |     if (error.response && error.response.statusCode === 429 && retries > 0) {
46 |       console.log(`Rate limited. Retrying after ${delay * 2}ms. Retries left: ${retries}`);
47 |       // Exponential backoff - double the delay for each retry
48 |       await sleep(delay * 2);
49 |       return makeApiCallWithRetry(apiCallFn, retries - 1, delay * 2);
50 |     }
51 |     
52 |     // If not rate limited or out of retries, rethrow
53 |     throw error;
54 |   }
55 | }
56 | 
57 | try {
58 |   // Check if required credentials are available
59 |   if (process.env.AMADEUS_CLIENT_ID && process.env.AMADEUS_CLIENT_SECRET) {
60 |     amadeus = new Amadeus({
61 |       clientId: process.env.AMADEUS_CLIENT_ID,
62 |       clientSecret: process.env.AMADEUS_CLIENT_SECRET
63 |     });
64 |     shouldRunIntegrationTests = true;
65 |     console.log('Amadeus API credentials found. Integration tests will run.');
66 |   } else {
67 |     console.log('Amadeus API credentials not found. Integration tests will be skipped.');
68 |     console.log('To run integration tests, set AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET in your .env file.');
69 |   }
70 | } catch (error) {
71 |   console.error('Failed to initialize Amadeus client:', error);
72 | }
73 | 
74 | /**
75 |  * Utility function to conditionally skip tests if credentials aren't available
76 |  * @param {Function} testFn - The Jest test function (test or it)
77 |  * @param {string} testName - The name of the test
78 |  * @param {Function} testCallback - The test callback function
79 |  */
80 | function conditionalTest(testFn, testName, testCallback) {
81 |   if (shouldRunIntegrationTests) {
82 |     testFn(testName, testCallback);
83 |   } else {
84 |     testFn.skip(testName, () => {
85 |       console.log(`Test "${testName}" skipped due to missing credentials`);
86 |     });
87 |   }
88 | }
89 | 
90 | module.exports = {
91 |   amadeus,
92 |   shouldRunIntegrationTests,
93 |   conditionalTest,
94 |   sleep,
95 |   makeApiCallWithRetry
96 | }; 
```

--------------------------------------------------------------------------------
/scripts/test-tools.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | const { spawn } = require('node:child_process');
  4 | const readline = require('node:readline');
  5 | 
  6 | // Helper function to get a future date string
  7 | const getFutureDate = (daysFromNow) => {
  8 |   const date = new Date();
  9 |   date.setDate(date.getDate() + daysFromNow);
 10 |   return date.toISOString().split('T')[0]; // YYYY-MM-DD format
 11 | };
 12 | 
 13 | // Predefined test cases
 14 | const testCases = {
 15 |   '1': {
 16 |     name: 'Search flights (JFK to LAX)',
 17 |     tool: 'search-flights',
 18 |     params: {
 19 |       originLocationCode: 'JFK',
 20 |       destinationLocationCode: 'LAX',
 21 |       departureDate: getFutureDate(90),
 22 |       adults: 1,
 23 |       children: 0,
 24 |       infants: 0,
 25 |       nonStop: false,
 26 |       currencyCode: 'USD',
 27 |       maxResults: 5
 28 |     }
 29 |   },
 30 |   '2': {
 31 |     name: 'Search airports (JFK)',
 32 |     tool: 'search-airports',
 33 |     params: {
 34 |       keyword: 'JFK',
 35 |       maxResults: 5
 36 |     }
 37 |   },
 38 |   '3': {
 39 |     name: 'Price analysis (JFK to LAX)',
 40 |     tool: 'flight-price-analysis',
 41 |     params: {
 42 |       originLocationCode: 'JFK',
 43 |       destinationLocationCode: 'LAX',
 44 |       departureDate: getFutureDate(90),
 45 |       currencyCode: 'USD'
 46 |     }
 47 |   },
 48 |   '4': {
 49 |     name: 'Find cheapest dates (JFK to LAX)',
 50 |     tool: 'find-cheapest-dates',
 51 |     params: {
 52 |       originLocationCode: 'JFK',
 53 |       destinationLocationCode: 'LAX',
 54 |       departureDate: getFutureDate(90),
 55 |       oneWay: true,
 56 |       nonStop: false,
 57 |       currencyCode: 'USD'
 58 |     }
 59 |   },
 60 |   '5': {
 61 |     name: 'Round-trip flights (BOS to LHR)',
 62 |     tool: 'search-flights',
 63 |     params: {
 64 |       originLocationCode: 'BOS',
 65 |       destinationLocationCode: 'LHR',
 66 |       departureDate: getFutureDate(90),
 67 |       returnDate: getFutureDate(97),
 68 |       adults: 1,
 69 |       children: 0,
 70 |       infants: 0,
 71 |       nonStop: false,
 72 |       currencyCode: 'USD',
 73 |       maxResults: 5
 74 |     }
 75 |   }
 76 | };
 77 | 
 78 | // Create readline interface
 79 | const rl = readline.createInterface({
 80 |   input: process.stdin,
 81 |   output: process.stdout
 82 | });
 83 | 
 84 | // Function to display menu
 85 | function displayMenu() {
 86 |   console.log('\nAvailable test cases:');
 87 |   for (const [key, test] of Object.entries(testCases)) {
 88 |     console.log(`${key}. ${test.name}`);
 89 |   }
 90 |   console.log('q. Quit');
 91 | }
 92 | 
 93 | // Function to run a test case
 94 | function runTest(testCase) {
 95 |   console.log(`\nRunning: ${testCase.name}`);
 96 |   console.log('Parameters:', JSON.stringify(testCase.params, null, 2));
 97 |   
 98 |   const inspector = spawn('npx', [
 99 |     '@modelcontextprotocol/inspector',
100 |     'node',
101 |     'dist/cli.js',
102 |     '--tool',
103 |     testCase.tool,
104 |     '--params',
105 |     JSON.stringify(testCase.params)
106 |   ]);
107 | 
108 |   inspector.stdout.on('data', (data) => {
109 |     console.log(`\n${data}`);
110 |   });
111 | 
112 |   inspector.stderr.on('data', (data) => {
113 |     console.error(`\nError: ${data}`);
114 |   });
115 | 
116 |   inspector.on('close', (code) => {
117 |     console.log(`\nTest completed with code ${code}`);
118 |     displayMenu();
119 |     askForInput();
120 |   });
121 | }
122 | 
123 | // Function to ask for input
124 | function askForInput() {
125 |   rl.question('\nEnter the number of the test case to run (or q to quit): ', (answer) => {
126 |     if (answer.toLowerCase() === 'q') {
127 |       rl.close();
128 |       return;
129 |     }
130 | 
131 |     const testCase = testCases[answer];
132 |     if (testCase) {
133 |       runTest(testCase);
134 |     } else {
135 |       console.log('Invalid selection. Please try again.');
136 |       displayMenu();
137 |       askForInput();
138 |     }
139 |   });
140 | }
141 | 
142 | // Start the program
143 | console.log('Amadeus MCP Server Tool Tester');
144 | console.log('=============================');
145 | displayMenu();
146 | askForInput(); 
```

--------------------------------------------------------------------------------
/__tests__/prompts.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { server } from '../src/index';
 2 | 
 3 | describe('MCP Prompts', () => {
 4 |   // Helper function to get a prompt handler
 5 |   const getPromptHandler = (promptName: string) => {
 6 |     const prompt = server.prompts.find(
 7 |       (p: { meta?: { name: string } }) => p.meta?.name === promptName,
 8 |     );
 9 |     return prompt?.handler;
10 |   };
11 | 
12 |   test('analyze-flight-prices prompt returns correct message structure', async () => {
13 |     const promptHandler = getPromptHandler('analyze-flight-prices');
14 |     expect(promptHandler).toBeDefined();
15 | 
16 |     if (promptHandler) {
17 |       const result = await promptHandler({
18 |         originLocationCode: 'JFK',
19 |         destinationLocationCode: 'LAX',
20 |         departureDate: '2023-01-01',
21 |         returnDate: '2023-01-10',
22 |       });
23 | 
24 |       expect(result).toHaveProperty('messages');
25 |       expect(result.messages).toHaveLength(1);
26 |       expect(result.messages[0]).toHaveProperty('role', 'user');
27 |       expect(result.messages[0].content).toHaveProperty('type', 'text');
28 | 
29 |       // Check that the text includes the parameters we provided
30 |       const text = result.messages[0].content.text;
31 |       expect(text).toContain('JFK');
32 |       expect(text).toContain('LAX');
33 |       expect(text).toContain('2023-01-01');
34 |       expect(text).toContain('2023-01-10');
35 |       expect(text).toContain('flight-price-analysis tool');
36 |       expect(text).toContain('search-flights tool');
37 |     }
38 |   });
39 | 
40 |   test('find-best-deals prompt returns correct message structure', async () => {
41 |     const promptHandler = getPromptHandler('find-best-deals');
42 |     expect(promptHandler).toBeDefined();
43 | 
44 |     if (promptHandler) {
45 |       const result = await promptHandler({
46 |         originLocationCode: 'JFK',
47 |         destinationLocationCode: 'LAX',
48 |         departureDate: '2023-01-01',
49 |         returnDate: '2023-01-10',
50 |         travelClass: 'BUSINESS',
51 |       });
52 | 
53 |       expect(result).toHaveProperty('messages');
54 |       expect(result.messages).toHaveLength(1);
55 |       expect(result.messages[0]).toHaveProperty('role', 'user');
56 |       expect(result.messages[0].content).toHaveProperty('type', 'text');
57 | 
58 |       // Check that the text includes the parameters we provided
59 |       const text = result.messages[0].content.text;
60 |       expect(text).toContain('JFK');
61 |       expect(text).toContain('LAX');
62 |       expect(text).toContain('2023-01-01');
63 |       expect(text).toContain('2023-01-10');
64 |       expect(text).toContain('BUSINESS class');
65 |       expect(text).toContain('search-flights tool');
66 |     }
67 |   });
68 | 
69 |   test('plan-multi-city-trip prompt returns correct message structure', async () => {
70 |     const promptHandler = getPromptHandler('plan-multi-city-trip');
71 |     expect(promptHandler).toBeDefined();
72 | 
73 |     if (promptHandler) {
74 |       const result = await promptHandler({
75 |         cities: 'LAX, SFO, LAS',
76 |         startDate: '2023-01-01',
77 |         endDate: '2023-01-15',
78 |         homeAirport: 'JFK',
79 |       });
80 | 
81 |       expect(result).toHaveProperty('messages');
82 |       expect(result.messages).toHaveLength(1);
83 |       expect(result.messages[0]).toHaveProperty('role', 'user');
84 |       expect(result.messages[0].content).toHaveProperty('type', 'text');
85 | 
86 |       // Check that the text includes the parameters we provided
87 |       const text = result.messages[0].content.text;
88 |       expect(text).toContain('LAX, SFO, LAS');
89 |       expect(text).toContain('JFK');
90 |       expect(text).toContain('2023-01-01');
91 |       expect(text).toContain('2023-01-15');
92 |       expect(text).toContain('search-airports tool');
93 |       expect(text).toContain('search-flights tool');
94 |     }
95 |   });
96 | });
97 | 
```

--------------------------------------------------------------------------------
/src/naturalLanguageHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { analyzeQuery } from './queryAnalyzer.js';
  2 | import { processQuery } from './queryProcessor.js';
  3 | import { server } from './index.js';
  4 | import { z } from 'zod';
  5 | 
  6 | // Interface for the natural language response
  7 | interface NLResponse {
  8 |   summary: string;
  9 |   details: any;
 10 |   suggestedActions: string[];
 11 |   followUpQuestions: string[];
 12 | }
 13 | 
 14 | // Format the response in natural language
 15 | function formatResponse(
 16 |   queryResult: any,
 17 |   analyzedQuery: any
 18 | ): NLResponse {
 19 |   const response: NLResponse = {
 20 |     summary: '',
 21 |     details: queryResult.mainResult,
 22 |     suggestedActions: [],
 23 |     followUpQuestions: queryResult.suggestedFollowUp || [],
 24 |   };
 25 | 
 26 |   // Generate summary based on query type
 27 |   switch (analyzedQuery.type) {
 28 |     case 'inspiration':
 29 |       response.summary = `I've found some interesting destinations you might like to visit${
 30 |         analyzedQuery.timeFrame.value ? ` during ${analyzedQuery.timeFrame.value}` : ''
 31 |       }${
 32 |         analyzedQuery.budget ? ` within your budget of ${analyzedQuery.budget.amount}` : ''
 33 |       }.`;
 34 |       break;
 35 |       
 36 |     case 'specific_route':
 37 |       response.summary = `Here are the best flight options from ${analyzedQuery.origin?.raw} to ${
 38 |         analyzedQuery.destinations[0]?.raw
 39 |       }${analyzedQuery.timeFrame.value ? ` on ${analyzedQuery.timeFrame.value}` : ''}.`;
 40 |       break;
 41 |       
 42 |     case 'multi_city':
 43 |       response.summary = `I've planned a multi-city trip visiting ${
 44 |         analyzedQuery.destinations.map(d => d.raw).join(', ')
 45 |       }${analyzedQuery.duration ? ` over ${analyzedQuery.duration.value} ${analyzedQuery.duration.type}` : ''}.`;
 46 |       break;
 47 |       
 48 |     case 'flexible_value':
 49 |       response.summary = `I've found the most economical travel options for your trip${
 50 |         analyzedQuery.destinations.length > 0 ? ` to ${analyzedQuery.destinations[0].raw}` : ''
 51 |       }.`;
 52 |       break;
 53 |   }
 54 | 
 55 |   // Generate suggested actions based on results
 56 |   if (queryResult.supplementaryResults) {
 57 |     response.suggestedActions = [
 58 |       'Compare prices across different dates',
 59 |       'Check alternative airports',
 60 |       'Set up price alerts',
 61 |       'View detailed flight information',
 62 |     ];
 63 |   }
 64 | 
 65 |   return response;
 66 | }
 67 | 
 68 | // Main handler for natural language queries
 69 | export async function handleNaturalLanguageQuery(
 70 |   query: string
 71 | ): Promise<NLResponse> {
 72 |   try {
 73 |     // Step 1: Analyze the natural language query
 74 |     const analyzed = await analyzeQuery(query);
 75 |     
 76 |     // Step 2: Process the analyzed query with appropriate tools
 77 |     const result = await processQuery(analyzed);
 78 |     
 79 |     // Step 3: Format the response in natural language
 80 |     const response = formatResponse(result, analyzed);
 81 |     
 82 |     // Add confidence-based disclaimer if needed
 83 |     if (analyzed.confidence < 0.8) {
 84 |       response.summary = `I'm not entirely sure, but ${response.summary.toLowerCase()} Please let me know if this isn't what you were looking for.`;
 85 |     }
 86 |     
 87 |     return response;
 88 |     
 89 |   } catch (error) {
 90 |     console.error('Error handling natural language query:', error);
 91 |     throw error;
 92 |   }
 93 | }
 94 | 
 95 | // Register the natural language prompt
 96 | server.prompt(
 97 |   'natural-language-travel',
 98 |   'Handle natural language travel queries',
 99 |   {
100 |     query: z.string().describe('The natural language travel query'),
101 |   },
102 |   async ({ query }: { query: string }) => {
103 |     const response = await handleNaturalLanguageQuery(query);
104 |     
105 |     return {
106 |       messages: [
107 |         {
108 |           role: 'assistant',
109 |           content: {
110 |             type: 'text',
111 |             text: `${response.summary}\n\n${
112 |               response.followUpQuestions.length > 0
113 |                 ? `To help me provide better results, could you please clarify:\n${response.followUpQuestions
114 |                     .map(q => `- ${q}`)
115 |                     .join('\n')}\n\n`
116 |                 : ''
117 |             }${
118 |               response.suggestedActions.length > 0
119 |                 ? `You can also:\n${response.suggestedActions
120 |                     .map(a => `- ${a}`)
121 |                     .join('\n')}`
122 |                 : ''
123 |             }`,
124 |           },
125 |         },
126 |       ],
127 |     };
128 |   },
129 | ); 
```

--------------------------------------------------------------------------------
/__tests__/integration/flight-search.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Integration tests for Flight Search API
  3 |  */
  4 | 
  5 | const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
  6 | 
  7 | describe('Flight Search API - Integration', () => {
  8 |   // Set longer timeout for API calls (60 seconds to account for retries)
  9 |   jest.setTimeout(60000);
 10 | 
 11 |   // Helper function to get a future date string
 12 |   const getFutureDate = (daysFromNow) => {
 13 |     const date = new Date();
 14 |     date.setDate(date.getDate() + daysFromNow);
 15 |     return date.toISOString().split('T')[0]; // YYYY-MM-DD format
 16 |   };
 17 | 
 18 |   conditionalTest(test, 'should find flights from JFK to LAX', async () => {
 19 |     expect(amadeus).not.toBeNull();
 20 | 
 21 |     // Set dates for 90 days from now to ensure it's in the future
 22 |     const departureDateStr = getFutureDate(90);
 23 | 
 24 |     // Parameters for the flight search
 25 |     const params = {
 26 |       originLocationCode: 'JFK',
 27 |       destinationLocationCode: 'LAX',
 28 |       departureDate: departureDateStr,
 29 |       adults: 1,
 30 |       max: 5, // Limit to 5 results for faster testing
 31 |     };
 32 | 
 33 |     try {
 34 |       console.log(`Searching for flights from JFK to LAX on ${departureDateStr}`);
 35 |       // Use the makeApiCallWithRetry helper to handle rate limiting
 36 |       const response = await makeApiCallWithRetry(() => 
 37 |         amadeus.shopping.flightOffersSearch.get(params)
 38 |       );
 39 |       
 40 |       // Basic validation
 41 |       expect(response).toBeDefined();
 42 |       expect(response.data).toBeDefined();
 43 |       expect(Array.isArray(response.data)).toBe(true);
 44 |       
 45 |       // We should find at least one flight
 46 |       expect(response.data.length).toBeGreaterThan(0);
 47 |       
 48 |       // First flight should depart from JFK
 49 |       const firstOffer = response.data[0];
 50 |       const firstSegment = firstOffer.itineraries[0].segments[0];
 51 |       expect(firstSegment.departure.iataCode).toBe('JFK');
 52 |       expect(firstSegment.arrival).toBeDefined();
 53 |       
 54 |       console.log(`Found ${response.data.length} flights from JFK`);
 55 |       console.log(`First flight: ${firstOffer.price.total} ${firstOffer.price.currency}`);
 56 |     } catch (error) {
 57 |       console.error('API Error:', error);
 58 |       throw error;
 59 |     }
 60 |   });
 61 | 
 62 |   conditionalTest(test, 'should find round-trip flights', async () => {
 63 |     expect(amadeus).not.toBeNull();
 64 | 
 65 |     // Set dates for 90 and 97 days from now (one week trip)
 66 |     const departureDateStr = getFutureDate(90);
 67 |     const returnDateStr = getFutureDate(97);
 68 | 
 69 |     // Parameters for the flight search
 70 |     const params = {
 71 |       originLocationCode: 'BOS',
 72 |       destinationLocationCode: 'LHR',
 73 |       departureDate: departureDateStr,
 74 |       returnDate: returnDateStr,
 75 |       adults: 1,
 76 |       max: 5, // Limit to 5 results for faster testing
 77 |     };
 78 | 
 79 |     try {
 80 |       console.log(`Searching for round-trip flights from BOS to LHR on ${departureDateStr} returning ${returnDateStr}`);
 81 |       // Use the makeApiCallWithRetry helper to handle rate limiting
 82 |       const response = await makeApiCallWithRetry(() => 
 83 |         amadeus.shopping.flightOffersSearch.get(params)
 84 |       );
 85 |       
 86 |       // Basic validation
 87 |       expect(response).toBeDefined();
 88 |       expect(response.data).toBeDefined();
 89 |       expect(Array.isArray(response.data)).toBe(true);
 90 |       
 91 |       // We should find at least one flight
 92 |       expect(response.data.length).toBeGreaterThan(0);
 93 |       
 94 |       // For round-trip, we should have 2 itineraries
 95 |       const firstOffer = response.data[0];
 96 |       expect(firstOffer.itineraries.length).toBe(2);
 97 |       
 98 |       // Outbound should be BOS to LHR
 99 |       const outbound = firstOffer.itineraries[0];
100 |       const outSegment = outbound.segments[0];
101 |       expect(outSegment.departure.iataCode).toBe('BOS');
102 |       
103 |       // Return should be LHR to BOS
104 |       const returnFlight = firstOffer.itineraries[1];
105 |       const returnSegment = returnFlight.segments[returnFlight.segments.length - 1];
106 |       expect(returnSegment.arrival.iataCode).toBe('BOS');
107 |       
108 |       console.log(`Found ${response.data.length} round-trip flights from BOS to LHR`);
109 |       console.log(`Price: ${firstOffer.price.total} ${firstOffer.price.currency}`);
110 |     } catch (error) {
111 |       console.error('API Error:', error);
112 |       throw error;
113 |     }
114 |   });
115 | }); 
```

--------------------------------------------------------------------------------
/src/queryProcessor.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { type AnalyzedQuery, TravelQueryType } from './queryAnalyzer.js';
  2 | import { server } from './index.js';
  3 | 
  4 | // Interface for tool selection and parameter mapping
  5 | interface ToolMapping {
  6 |   primaryTool: string;
  7 |   secondaryTools?: string[];
  8 |   parameterMap: (query: AnalyzedQuery) => Promise<Record<string, any>>;
  9 | }
 10 | 
 11 | // Map query types to appropriate tools and parameter mappings
 12 | const toolMappings: Record<TravelQueryType, ToolMapping> = {
 13 |   [TravelQueryType.INSPIRATION]: {
 14 |     primaryTool: 'discover-destinations',
 15 |     secondaryTools: ['search-flights', 'search-airports'],
 16 |     async parameterMap(query) {
 17 |       return {
 18 |         originLocationCode: query.origin?.code || '',
 19 |         maxPrice: query.budget?.amount,
 20 |         departureDate: query.timeFrame.value,
 21 |         tripDuration: query.duration?.value.toString(),
 22 |       };
 23 |     },
 24 |   },
 25 |   
 26 |   [TravelQueryType.SPECIFIC_ROUTE]: {
 27 |     primaryTool: 'find-best-deals',
 28 |     secondaryTools: ['analyze-flight-prices', 'find-cheapest-travel-dates'],
 29 |     async parameterMap(query) {
 30 |       return {
 31 |         originLocationCode: query.origin?.code || '',
 32 |         destinationLocationCode: query.destinations[0]?.code || '',
 33 |         departureDate: Array.isArray(query.timeFrame.value) 
 34 |           ? query.timeFrame.value[0] 
 35 |           : query.timeFrame.value,
 36 |         returnDate: Array.isArray(query.timeFrame.value) 
 37 |           ? query.timeFrame.value[1] 
 38 |           : undefined,
 39 |         travelClass: query.preferences?.class?.toUpperCase(),
 40 |       };
 41 |     },
 42 |   },
 43 |   
 44 |   [TravelQueryType.MULTI_CITY]: {
 45 |     primaryTool: 'plan-multi-city-trip',
 46 |     secondaryTools: ['search-flights', 'airport-routes'],
 47 |     async parameterMap(query) {
 48 |       const cities = query.destinations.map(dest => dest.code).join(',');
 49 |       return {
 50 |         cities,
 51 |         startDate: Array.isArray(query.timeFrame.value) 
 52 |           ? query.timeFrame.value[0] 
 53 |           : query.timeFrame.value,
 54 |         endDate: Array.isArray(query.timeFrame.value) 
 55 |           ? query.timeFrame.value[1] 
 56 |           : '',
 57 |         homeAirport: query.origin?.code || '',
 58 |       };
 59 |     },
 60 |   },
 61 |   
 62 |   [TravelQueryType.FLEXIBLE_VALUE]: {
 63 |     primaryTool: 'find-cheapest-travel-dates',
 64 |     secondaryTools: ['analyze-flight-prices'],
 65 |     async parameterMap(query) {
 66 |       return {
 67 |         originLocationCode: query.origin?.code || '',
 68 |         destinationLocationCode: query.destinations[0]?.code || '',
 69 |         earliestDepartureDate: Array.isArray(query.timeFrame.value) 
 70 |           ? query.timeFrame.value[0] 
 71 |           : query.timeFrame.value,
 72 |         latestDepartureDate: Array.isArray(query.timeFrame.value) 
 73 |           ? query.timeFrame.value[1] 
 74 |           : '',
 75 |         tripDuration: query.duration?.value.toString(),
 76 |       };
 77 |     },
 78 |   },
 79 | };
 80 | 
 81 | // Process the analyzed query and call appropriate tools
 82 | export async function processQuery(
 83 |   analyzedQuery: AnalyzedQuery
 84 | ): Promise<{
 85 |   mainResult: any;
 86 |   supplementaryResults?: Record<string, any>;
 87 |   confidence: number;
 88 |   suggestedFollowUp?: string[];
 89 | }> {
 90 |   const mapping = toolMappings[analyzedQuery.type];
 91 |   
 92 |   if (!mapping) {
 93 |     throw new Error(`No tool mapping found for query type: ${analyzedQuery.type}`);
 94 |   }
 95 |   
 96 |   try {
 97 |     // Map the analyzed query to tool parameters
 98 |     const params = await mapping.parameterMap(analyzedQuery);
 99 |     
100 |     // Call the primary tool
101 |     const mainResult = await server.prompt(mapping.primaryTool, () => params);
102 |     
103 |     // Call secondary tools if needed and if confidence is high enough
104 |     const supplementaryResults: Record<string, any> = {};
105 |     if (mapping.secondaryTools && analyzedQuery.confidence > 0.7) {
106 |       for (const tool of mapping.secondaryTools) {
107 |         try {
108 |           supplementaryResults[tool] = await server.prompt(tool, () => params);
109 |         } catch (error) {
110 |           console.error(`Error running secondary tool ${tool}:`, error);
111 |         }
112 |       }
113 |     }
114 |     
115 |     // Generate follow-up suggestions based on ambiguities or missing information
116 |     const suggestedFollowUp = generateFollowUpQuestions(analyzedQuery);
117 |     
118 |     return {
119 |       mainResult,
120 |       supplementaryResults: Object.keys(supplementaryResults).length > 0 
121 |         ? supplementaryResults 
122 |         : undefined,
123 |       confidence: analyzedQuery.confidence,
124 |       suggestedFollowUp,
125 |     };
126 |     
127 |   } catch (error) {
128 |     console.error('Error processing query:', error);
129 |     throw error;
130 |   }
131 | }
132 | 
133 | // Generate follow-up questions based on analysis
134 | function generateFollowUpQuestions(query: AnalyzedQuery): string[] {
135 |   const questions: string[] = [];
136 |   
137 |   // Check for missing or ambiguous information
138 |   if (!query.origin?.code) {
139 |     questions.push('Which city would you like to depart from?');
140 |   }
141 |   
142 |   if (query.timeFrame.isFlexible) {
143 |     questions.push('Do you have specific dates in mind for your travel?');
144 |   }
145 |   
146 |   if (!query.budget && query.type !== TravelQueryType.SPECIFIC_ROUTE) {
147 |     questions.push('Do you have a budget in mind for this trip?');
148 |   }
149 |   
150 |   if (!query.duration && query.type !== TravelQueryType.SPECIFIC_ROUTE) {
151 |     questions.push('How long would you like to travel for?');
152 |   }
153 |   
154 |   if (query.ambiguities) {
155 |     questions.push(...query.ambiguities);
156 |   }
157 |   
158 |   return questions;
159 | }
160 | 
161 | // Example usage:
162 | // const query = "I want to go somewhere warm in December for about a week";
163 | // const analyzed = await analyzeQuery(query);
164 | // const result = await processQuery(analyzed); 
```

--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Schema resources
  2 | import { server } from './index.js';
  3 | 
  4 | server.resource(
  5 |   'flight-offer-schema',
  6 |   'schema://flight-offers',
  7 |   async (uri) => {
  8 |     const schema = {
  9 |       type: 'Flight Offer',
 10 |       properties: {
 11 |         type: 'The type of the offer (e.g., flight-offer)',
 12 |         id: 'Unique identifier for the offer',
 13 |         source: 'The source of the offer',
 14 |         instantTicketingRequired: 'Whether instant ticketing is required',
 15 |         nonHomogeneous: 'Whether the offer is non-homogeneous',
 16 |         oneWay: 'Whether the offer is one-way',
 17 |         lastTicketingDate: 'The last date for ticketing',
 18 |         numberOfBookableSeats: 'Number of bookable seats',
 19 |         itineraries: 'Array of flight segments',
 20 |         price: 'Price information including total and currency',
 21 |         pricingOptions: 'Options related to pricing',
 22 |         validatingAirlineCodes: 'Codes of validating airlines',
 23 |         travelerPricings: 'Pricing information per traveler',
 24 |       },
 25 |     };
 26 | 
 27 |     return {
 28 |       contents: [
 29 |         {
 30 |           uri: uri.href,
 31 |           text: JSON.stringify(schema, null, 2),
 32 |           mimeType: 'application/json',
 33 |         },
 34 |       ],
 35 |     };
 36 |   },
 37 | );
 38 | 
 39 | // Airport schema
 40 | server.resource('airport-schema', 'schema://airports', async (uri) => {
 41 |   const schema = {
 42 |     type: 'Airport',
 43 |     properties: {
 44 |       iataCode: 'IATA three-letter airport code',
 45 |       name: 'Full name of the airport',
 46 |       cityCode: 'City code',
 47 |       cityName: 'City name',
 48 |       countryCode: 'Two-letter country code',
 49 |       countryName: 'Country name',
 50 |       latitude: 'Geographic latitude',
 51 |       longitude: 'Geographic longitude',
 52 |       timezone: 'Timezone of the airport',
 53 |     },
 54 |   };
 55 | 
 56 |   return {
 57 |     contents: [
 58 |       {
 59 |         uri: uri.href,
 60 |         text: JSON.stringify(schema, null, 2),
 61 |         mimeType: 'application/json',
 62 |       },
 63 |     ],
 64 |   };
 65 | });
 66 | 
 67 | // Flight Inspiration schema
 68 | server.resource(
 69 |   'flight-inspiration-schema',
 70 |   'schema://flight-inspiration',
 71 |   async (uri) => {
 72 |     const schema = {
 73 |       type: 'Flight Inspiration',
 74 |       properties: {
 75 |         type: 'Type of the result',
 76 |         origin: 'IATA code of the origin city/airport',
 77 |         destination: 'IATA code of the destination city/airport',
 78 |         departureDate: 'Date of departure',
 79 |         returnDate: 'Date of return (for round trips)',
 80 |         price: {
 81 |           description: 'Price information',
 82 |           properties: {
 83 |             total: 'Total price of the flight',
 84 |           },
 85 |         },
 86 |         links: {
 87 |           description: 'Related links',
 88 |           properties: {
 89 |             flightDates: 'Link to flight dates',
 90 |             flightOffers: 'Link to flight offers',
 91 |           },
 92 |         },
 93 |       },
 94 |     };
 95 | 
 96 |     return {
 97 |       contents: [
 98 |         {
 99 |           uri: uri.href,
100 |           text: JSON.stringify(schema, null, 2),
101 |           mimeType: 'application/json',
102 |         },
103 |       ],
104 |     };
105 |   },
106 | );
107 | 
108 | // Airport Routes schema
109 | server.resource(
110 |   'airport-routes-schema',
111 |   'schema://airport-routes',
112 |   async (uri) => {
113 |     const schema = {
114 |       type: 'Airport Route',
115 |       properties: {
116 |         type: 'Type of the route',
117 |         subtype: 'Subtype of the route',
118 |         name: 'Name of the destination',
119 |         iataCode: 'IATA code of the destination',
120 |         distance: {
121 |           description: 'Distance information',
122 |           properties: {
123 |             value: 'Numeric value of the distance',
124 |             unit: 'Unit of measurement',
125 |           },
126 |         },
127 |         analytics: {
128 |           description: 'Analytics information',
129 |           properties: {
130 |             flights: {
131 |               description: 'Flight statistics',
132 |               properties: {
133 |                 score: 'Flight frequency score',
134 |               },
135 |             },
136 |             travelers: {
137 |               description: 'Traveler statistics',
138 |               properties: {
139 |                 score: 'Traveler volume score',
140 |               },
141 |             },
142 |           },
143 |         },
144 |       },
145 |     };
146 | 
147 |     return {
148 |       contents: [
149 |         {
150 |           uri: uri.href,
151 |           text: JSON.stringify(schema, null, 2),
152 |           mimeType: 'application/json',
153 |         },
154 |       ],
155 |     };
156 |   },
157 | );
158 | 
159 | // Nearest Airport schema
160 | server.resource(
161 |   'nearest-airport-schema',
162 |   'schema://nearest-airports',
163 |   async (uri) => {
164 |     const schema = {
165 |       type: 'Nearest Airport',
166 |       properties: {
167 |         type: 'Type of the location',
168 |         subtype: 'Subtype of the location (e.g., AIRPORT)',
169 |         name: 'Name of the airport',
170 |         detailedName: 'Detailed name including location',
171 |         iataCode: 'IATA code of the airport',
172 |         distance: {
173 |           description: 'Distance from search point',
174 |           properties: {
175 |             value: 'Numeric value of the distance',
176 |             unit: 'Unit of measurement',
177 |           },
178 |         },
179 |         analytics: {
180 |           description: 'Analytics information',
181 |           properties: {
182 |             flights: {
183 |               description: 'Flight statistics',
184 |               properties: {
185 |                 score: 'Flight frequency score',
186 |               },
187 |             },
188 |             travelers: {
189 |               description: 'Traveler statistics',
190 |               properties: {
191 |                 score: 'Traveler volume score',
192 |               },
193 |             },
194 |           },
195 |         },
196 |       },
197 |     };
198 | 
199 |     return {
200 |       contents: [
201 |         {
202 |           uri: uri.href,
203 |           text: JSON.stringify(schema, null, 2),
204 |           mimeType: 'application/json',
205 |         },
206 |       ],
207 |     };
208 |   },
209 | );
210 | 
```

--------------------------------------------------------------------------------
/__tests__/integration/price-analysis.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Integration tests for Flight Price Analysis API
  3 |  */
  4 | 
  5 | const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
  6 | 
  7 | describe('Flight Price Analysis API - Integration', () => {
  8 |   // Set longer timeout for API calls (60 seconds to account for retries)
  9 |   jest.setTimeout(60000);
 10 | 
 11 |   conditionalTest(test, 'should get price analysis for JFK to LAX route', async () => {
 12 |     expect(amadeus).not.toBeNull();
 13 | 
 14 |     // Set dates for 30 days from now
 15 |     const departureDate = new Date();
 16 |     departureDate.setDate(departureDate.getDate() + 30);
 17 |     const departureDateStr = departureDate.toISOString().split('T')[0]; // YYYY-MM-DD format
 18 | 
 19 |     // Parameters for the price analysis - using correct parameter names
 20 |     const params = {
 21 |       originIataCode: 'JFK',
 22 |       destinationIataCode: 'LAX',
 23 |       departureDate: departureDateStr,
 24 |       currencyCode: 'USD'
 25 |     };
 26 | 
 27 |     try {
 28 |       console.log(`Getting price analysis for JFK to LAX on ${departureDateStr}`);
 29 |       // Use the makeApiCallWithRetry helper to handle rate limiting
 30 |       const response = await makeApiCallWithRetry(() => 
 31 |         amadeus.analytics.itineraryPriceMetrics.get(params)
 32 |       );
 33 |       
 34 |       // Basic validation
 35 |       expect(response).toBeDefined();
 36 |       expect(response.data).toBeDefined();
 37 |       expect(Array.isArray(response.data)).toBe(true);
 38 |       
 39 |       // Should find price data
 40 |       expect(response.data.length).toBeGreaterThan(0);
 41 |       
 42 |       // Check the first result - the API response format may have changed
 43 |       const analysis = response.data[0];
 44 | 
 45 |       // The origin might be an object with iataCode instead of a string
 46 |       if (typeof analysis.origin === 'object' && analysis.origin.iataCode) {
 47 |         expect(analysis.origin.iataCode).toBe('JFK');
 48 |       } else {
 49 |         expect(analysis.origin).toBe('JFK');
 50 |       }
 51 | 
 52 |       // Similarly, check for destination 
 53 |       if (typeof analysis.destination === 'object' && analysis.destination.iataCode) {
 54 |         expect(analysis.destination.iataCode).toBe('LAX');
 55 |       } else {
 56 |         expect(analysis.destination).toBe('LAX');
 57 |       }
 58 | 
 59 |       // The departure date should still match
 60 |       expect(analysis.departureDate).toBe(departureDateStr);
 61 |       expect(analysis.priceMetrics).toBeDefined();
 62 |       expect(Array.isArray(analysis.priceMetrics)).toBe(true);
 63 |       expect(analysis.priceMetrics.length).toBeGreaterThan(0);
 64 |       
 65 |       // Check price metrics structure
 66 |       const firstMetric = analysis.priceMetrics[0];
 67 |       expect(firstMetric.amount).toBeDefined();
 68 |       expect(firstMetric.quartileRanking).toBeDefined();
 69 |       
 70 |       console.log(`Price metrics: ${analysis.priceMetrics.map(m => `${m.quartileRanking}: ${m.amount}`).join(', ')}`);
 71 |     } catch (error) {
 72 |       // The price analysis API might not have data for all routes/dates
 73 |       // If we get a 404, we'll just skip this test
 74 |       if (error.code === 404 || error.response?.statusCode === 404) {
 75 |         console.log('Price analysis data not available for this route/date, skipping test');
 76 |         return; // Skip but don't fail
 77 |       }
 78 |       
 79 |       console.error('API Error:', error);
 80 |       throw error;
 81 |     }
 82 |   });
 83 | 
 84 |   conditionalTest(test, 'should get price analysis for a popular route (NYC to LON)', async () => {
 85 |     expect(amadeus).not.toBeNull();
 86 | 
 87 |     // Set dates for 45 days from now (more likely to have data for popular routes)
 88 |     const departureDate = new Date();
 89 |     departureDate.setDate(departureDate.getDate() + 45);
 90 |     const departureDateStr = departureDate.toISOString().split('T')[0];
 91 | 
 92 |     // Parameters for the price analysis - using correct parameter names
 93 |     const params = {
 94 |       originIataCode: 'NYC', // New York City (all airports)
 95 |       destinationIataCode: 'LON', // London (all airports)
 96 |       departureDate: departureDateStr,
 97 |       currencyCode: 'USD'
 98 |     };
 99 | 
100 |     try {
101 |       console.log(`Getting price analysis for NYC to LON on ${departureDateStr}`);
102 |       // Use the makeApiCallWithRetry helper to handle rate limiting
103 |       const response = await makeApiCallWithRetry(() => 
104 |         amadeus.analytics.itineraryPriceMetrics.get(params)
105 |       );
106 |       
107 |       // Basic validation
108 |       expect(response).toBeDefined();
109 |       expect(response.data).toBeDefined();
110 |       expect(Array.isArray(response.data)).toBe(true);
111 |       
112 |       // For this test, let's skip the expectations if no data is returned
113 |       // Popular routes should have data, but different test environments may return different results
114 |       if (response.data.length === 0) {
115 |         console.log('No price analysis data found for NYC to LON, skipping validation');
116 |         return;
117 |       }
118 |       
119 |       // Log the results
120 |       for (const analysis of response.data) {
121 |         // Get origin and destination values, handling both object and string formats
122 |         const origin = typeof analysis.origin === 'object' ? analysis.origin.iataCode : analysis.origin;
123 |         const destination = typeof analysis.destination === 'object' ? analysis.destination.iataCode : analysis.destination;
124 |         
125 |         console.log(`Analysis for ${origin} to ${destination} on ${analysis.departureDate}:`);
126 |         for (const metric of analysis.priceMetrics) {
127 |           console.log(`  ${metric.quartileRanking}: ${metric.amount}`);
128 |         }
129 |       }
130 |     } catch (error) {
131 |       // If we get a 404, we'll just skip this test
132 |       if (error.code === 404 || error.response?.statusCode === 404) {
133 |         console.log('Price analysis data not available for this route/date, skipping test');
134 |         return; // Skip but don't fail
135 |       }
136 |       
137 |       console.error('API Error:', error);
138 |       throw error;
139 |     }
140 |   });
141 | }); 
```

--------------------------------------------------------------------------------
/__tests__/tools.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { amadeus } from '../src/index';
  2 | import { server } from '../src/index';
  3 | 
  4 | // Mock the Amadeus API methods
  5 | jest.mock('amadeus', () => {
  6 |   return jest.fn().mockImplementation(() => {
  7 |     return {
  8 |       shopping: {
  9 |         flightOffers: {
 10 |           get: jest.fn().mockResolvedValue({
 11 |             data: [
 12 |               {
 13 |                 type: 'flight-offer',
 14 |                 id: '1',
 15 |                 price: { total: '200.00', currency: 'USD' },
 16 |                 itineraries: [
 17 |                   {
 18 |                     duration: 'PT5H30M',
 19 |                     segments: [
 20 |                       {
 21 |                         departure: {
 22 |                           iataCode: 'JFK',
 23 |                           at: '2023-01-01T08:00:00',
 24 |                         },
 25 |                         arrival: { iataCode: 'LAX', at: '2023-01-01T13:30:00' },
 26 |                         carrierCode: 'AA',
 27 |                         number: '123',
 28 |                       },
 29 |                     ],
 30 |                   },
 31 |                 ],
 32 |                 validatingAirlineCodes: ['AA'],
 33 |                 numberOfBookableSeats: 10,
 34 |               },
 35 |             ],
 36 |           }),
 37 |         },
 38 |       },
 39 |       referenceData: {
 40 |         locations: {
 41 |           get: jest.fn().mockResolvedValue({
 42 |             data: [
 43 |               {
 44 |                 type: 'location',
 45 |                 subType: 'AIRPORT',
 46 |                 name: 'John F Kennedy International Airport',
 47 |                 iataCode: 'JFK',
 48 |                 city: 'NEW YORK',
 49 |                 countryCode: 'US',
 50 |               },
 51 |             ],
 52 |           }),
 53 |         },
 54 |       },
 55 |       analytics: {
 56 |         itineraryPriceMetrics: {
 57 |           get: jest.fn().mockResolvedValue({
 58 |             data: [
 59 |               {
 60 |                 type: 'analytics',
 61 |                 origin: 'JFK',
 62 |                 destination: 'LAX',
 63 |                 departureDate: '2023-01-01',
 64 |                 priceMetrics: [
 65 |                   {
 66 |                     amount: '200.00',
 67 |                     quartileRanking: 'MINIMUM',
 68 |                   },
 69 |                 ],
 70 |               },
 71 |             ],
 72 |           }),
 73 |         },
 74 |       },
 75 |     };
 76 |   });
 77 | });
 78 | 
 79 | // Mock console.error to avoid cluttering test output
 80 | console.error = jest.fn();
 81 | 
 82 | describe('Amadeus API Tools', () => {
 83 |   beforeEach(() => {
 84 |     jest.clearAllMocks();
 85 |   });
 86 | 
 87 |   // Helper function to get a tool handler
 88 |   const getToolHandler = (toolName: string) => {
 89 |     const tool = server.tools.find(
 90 |       (t: { meta?: { name: string } }) => t.meta?.name === toolName,
 91 |     );
 92 |     return tool?.handler;
 93 |   };
 94 | 
 95 |   test('search-flights tool returns formatted flight data', async () => {
 96 |     const searchFlightsHandler = getToolHandler('search-flights');
 97 |     expect(searchFlightsHandler).toBeDefined();
 98 | 
 99 |     if (searchFlightsHandler) {
100 |       const result = await searchFlightsHandler({
101 |         originLocationCode: 'JFK',
102 |         destinationLocationCode: 'LAX',
103 |         departureDate: '2023-01-01',
104 |         adults: 1,
105 |         children: 0,
106 |         infants: 0,
107 |         nonStop: false,
108 |         currencyCode: 'USD',
109 |         maxResults: 10,
110 |       });
111 | 
112 |       expect(result).toHaveProperty('content');
113 |       expect(result.content[0]).toHaveProperty('text');
114 | 
115 |       // Parse the JSON text to check the formatted data
116 |       const formattedData = JSON.parse(result.content[0].text);
117 |       expect(formattedData).toHaveLength(1);
118 |       expect(formattedData[0]).toHaveProperty('price', '200.00 USD');
119 |       expect(formattedData[0]).toHaveProperty('bookableSeats', 10);
120 |       expect(formattedData[0]).toHaveProperty('airlines', 'AA');
121 |       expect(formattedData[0]).toHaveProperty('itineraries');
122 |       expect(formattedData[0].itineraries[0]).toHaveProperty(
123 |         'type',
124 |         'Outbound',
125 |       );
126 |       expect(formattedData[0].itineraries[0]).toHaveProperty(
127 |         'duration',
128 |         '5h 30m',
129 |       );
130 |       expect(formattedData[0].itineraries[0]).toHaveProperty(
131 |         'stops',
132 |         'Non-stop',
133 |       );
134 |     }
135 |   });
136 | 
137 |   test('search-airports tool returns airport data', async () => {
138 |     const searchAirportsHandler = getToolHandler('search-airports');
139 |     expect(searchAirportsHandler).toBeDefined();
140 | 
141 |     if (searchAirportsHandler) {
142 |       const result = await searchAirportsHandler({
143 |         keyword: 'JFK',
144 |         maxResults: 10,
145 |       });
146 | 
147 |       expect(result).toHaveProperty('content');
148 |       expect(result.content[0]).toHaveProperty('text');
149 | 
150 |       // Parse the JSON text to check the data
151 |       const data = JSON.parse(result.content[0].text);
152 |       expect(data).toHaveLength(1);
153 |       expect(data[0]).toHaveProperty('iataCode', 'JFK');
154 |       expect(data[0]).toHaveProperty(
155 |         'name',
156 |         'John F Kennedy International Airport',
157 |       );
158 |     }
159 |   });
160 | 
161 |   test('flight-price-analysis tool returns price metrics', async () => {
162 |     const priceAnalysisHandler = getToolHandler('flight-price-analysis');
163 |     expect(priceAnalysisHandler).toBeDefined();
164 | 
165 |     if (priceAnalysisHandler) {
166 |       const result = await priceAnalysisHandler({
167 |         originLocationCode: 'JFK',
168 |         destinationLocationCode: 'LAX',
169 |         departureDate: '2023-01-01',
170 |         currencyCode: 'USD',
171 |       });
172 | 
173 |       expect(result).toHaveProperty('content');
174 |       expect(result.content[0]).toHaveProperty('text');
175 | 
176 |       // Parse the JSON text to check the data
177 |       const data = JSON.parse(result.content[0].text);
178 |       expect(data).toHaveLength(1);
179 |       expect(data[0]).toHaveProperty('origin', 'JFK');
180 |       expect(data[0]).toHaveProperty('destination', 'LAX');
181 |       expect(data[0]).toHaveProperty('priceMetrics');
182 |       expect(data[0].priceMetrics[0]).toHaveProperty('amount', '200.00');
183 |     }
184 |   });
185 | });
186 | 
```

--------------------------------------------------------------------------------
/__tests__/amadeus-mock.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | // Mock for Amadeus API
  2 | const mockAmadeus = {
  3 |   shopping: {
  4 |     flightOffers: {
  5 |       get: jest.fn().mockResolvedValue({
  6 |         data: [
  7 |           {
  8 |             type: 'flight-offer',
  9 |             id: '1',
 10 |             price: { total: '200.00', currency: 'USD' },
 11 |             itineraries: [
 12 |               {
 13 |                 duration: 'PT5H30M',
 14 |                 segments: [
 15 |                   {
 16 |                     departure: { iataCode: 'JFK', at: '2023-01-01T08:00:00' },
 17 |                     arrival: { iataCode: 'LAX', at: '2023-01-01T13:30:00' },
 18 |                     carrierCode: 'AA',
 19 |                     number: '123'
 20 |                   }
 21 |                 ]
 22 |               }
 23 |             ],
 24 |             validatingAirlineCodes: ['AA'],
 25 |             numberOfBookableSeats: 10
 26 |           }
 27 |         ]
 28 |       })
 29 |     }
 30 |   },
 31 |   referenceData: {
 32 |     locations: {
 33 |       get: jest.fn().mockResolvedValue({
 34 |         data: [
 35 |           {
 36 |             type: 'location',
 37 |             subType: 'AIRPORT',
 38 |             name: 'John F Kennedy International Airport',
 39 |             iataCode: 'JFK',
 40 |             city: 'NEW YORK',
 41 |             countryCode: 'US'
 42 |           }
 43 |         ]
 44 |       })
 45 |     }
 46 |   },
 47 |   analytics: {
 48 |     itineraryPriceMetrics: {
 49 |       get: jest.fn().mockResolvedValue({
 50 |         data: [
 51 |           {
 52 |             type: 'analytics',
 53 |             origin: 'JFK',
 54 |             destination: 'LAX',
 55 |             departureDate: '2023-01-01',
 56 |             priceMetrics: [
 57 |               {
 58 |                 amount: '200.00',
 59 |                 quartileRanking: 'MINIMUM'
 60 |               }
 61 |             ]
 62 |           }
 63 |         ]
 64 |       })
 65 |     }
 66 |   }
 67 | };
 68 | 
 69 | // Mock the tool handler implementation
 70 | describe('Amadeus API Tools', () => {
 71 |   // Mock Search Flights Tool
 72 |   test('search-flights tool returns formatted flight data', async () => {
 73 |     // Mock implementation of search-flights tool handler
 74 |     const searchFlightsHandler = async (params) => {
 75 |       const response = await mockAmadeus.shopping.flightOffers.get();
 76 |       
 77 |       const formattedResults = response.data.map((offer) => {
 78 |         const {
 79 |           price,
 80 |           itineraries,
 81 |           validatingAirlineCodes,
 82 |           numberOfBookableSeats,
 83 |         } = offer;
 84 | 
 85 |         // Format itineraries with more details
 86 |         const formattedItineraries = itineraries.map((itinerary, idx) => {
 87 |           // Calculate duration in hours and minutes
 88 |           const durationStr = itinerary.duration.slice(2, -1); // Remove 'PT' and 'M'
 89 |           let hours = 0;
 90 |           let minutes = 0;
 91 |           
 92 |           if (durationStr.includes('H')) {
 93 |             const parts = durationStr.split('H');
 94 |             hours = Number.parseInt(parts[0]);
 95 |             if (parts[1]) {
 96 |               minutes = Number.parseInt(parts[1]);
 97 |             }
 98 |           } else {
 99 |             minutes = Number.parseInt(durationStr);
100 |           }
101 |           
102 |           const formattedDuration = `${hours}h ${minutes}m`;
103 | 
104 |           // Format stops
105 |           const numStops = itinerary.segments.length - 1;
106 |           const stopsText = numStops === 0 ? 'Non-stop' : `${numStops} stop${numStops > 1 ? 's' : ''}`;
107 | 
108 |           return {
109 |             type: idx === 0 ? 'Outbound' : 'Return',
110 |             duration: formattedDuration,
111 |             stops: stopsText,
112 |             segments: itinerary.segments.map(segment => 
113 |               `${segment.departure.iataCode} → ${segment.arrival.iataCode} - ${segment.carrierCode}${segment.number}`
114 |             ).join(' | ')
115 |           };
116 |         });
117 | 
118 |         return {
119 |           price: `${price.total} ${price.currency}`,
120 |           bookableSeats: numberOfBookableSeats || 'Unknown',
121 |           airlines: validatingAirlineCodes.join(', '),
122 |           itineraries: formattedItineraries,
123 |         };
124 |       });
125 | 
126 |       return {
127 |         content: [
128 |           {
129 |             type: 'text',
130 |             text: JSON.stringify(formattedResults, null, 2)
131 |           }
132 |         ]
133 |       };
134 |     };
135 | 
136 |     // Call the handler with test parameters
137 |     const result = await searchFlightsHandler({
138 |       originLocationCode: 'JFK',
139 |       destinationLocationCode: 'LAX',
140 |       departureDate: '2023-01-01',
141 |       adults: 1,
142 |       children: 0,
143 |       infants: 0,
144 |       nonStop: false,
145 |       currencyCode: 'USD',
146 |       maxResults: 10
147 |     });
148 | 
149 |     // Parse the JSON text to check the formatted data
150 |     const formattedData = JSON.parse(result.content[0].text);
151 |     
152 |     // Assertions
153 |     expect(formattedData).toHaveLength(1);
154 |     expect(formattedData[0]).toHaveProperty('price', '200.00 USD');
155 |     expect(formattedData[0]).toHaveProperty('bookableSeats', 10);
156 |     expect(formattedData[0]).toHaveProperty('airlines', 'AA');
157 |     expect(formattedData[0].itineraries[0]).toHaveProperty('type', 'Outbound');
158 |     expect(formattedData[0].itineraries[0]).toHaveProperty('duration', '5h 30m');
159 |     expect(formattedData[0].itineraries[0]).toHaveProperty('stops', 'Non-stop');
160 |   });
161 | 
162 |   // Mock Airport Search Tool
163 |   test('search-airports tool returns airport data', async () => {
164 |     // Mock implementation of search-airports tool handler
165 |     const searchAirportsHandler = async (params) => {
166 |       const response = await mockAmadeus.referenceData.locations.get();
167 |       
168 |       return {
169 |         content: [
170 |           {
171 |             type: 'text',
172 |             text: JSON.stringify(response.data, null, 2)
173 |           }
174 |         ]
175 |       };
176 |     };
177 | 
178 |     // Call the handler with test parameters
179 |     const result = await searchAirportsHandler({
180 |       keyword: 'JFK',
181 |       maxResults: 10
182 |     });
183 | 
184 |     // Parse the JSON text to check the data
185 |     const data = JSON.parse(result.content[0].text);
186 |     
187 |     // Assertions
188 |     expect(data).toHaveLength(1);
189 |     expect(data[0]).toHaveProperty('iataCode', 'JFK');
190 |     expect(data[0]).toHaveProperty('name', 'John F Kennedy International Airport');
191 |   });
192 | }); 
```

--------------------------------------------------------------------------------
/src/queryAnalyzer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | 
  3 | // Types of travel queries we can handle
  4 | export enum TravelQueryType {
  5 |   INSPIRATION = 'inspiration',           // "Where can I go..."
  6 |   SPECIFIC_ROUTE = 'specific_route',     // "Flights from X to Y"
  7 |   MULTI_CITY = 'multi_city',            // "Visit A, B, and C"
  8 |   FLEXIBLE_VALUE = 'flexible_value',     // "Cheapest time to fly..."
  9 | }
 10 | 
 11 | // Time expressions in queries
 12 | export enum TimeFrame {
 13 |   SPECIFIC_DATE = 'specific_date',       // "on June 15th"
 14 |   DATE_RANGE = 'date_range',            // "between June and July"
 15 |   MONTH = 'month',                      // "in December"
 16 |   SEASON = 'season',                    // "during summer"
 17 |   HOLIDAY = 'holiday',                  // "for Christmas"
 18 |   RELATIVE = 'relative',                // "next month"
 19 |   FLEXIBLE = 'flexible',                // "whenever it's cheapest"
 20 | }
 21 | 
 22 | // Weather/Climate preferences
 23 | export enum ClimatePreference {
 24 |   WARM = 'warm',
 25 |   COLD = 'cold',
 26 |   TROPICAL = 'tropical',
 27 |   MILD = 'mild',
 28 |   SUNNY = 'sunny',
 29 |   ANY = 'any',
 30 | }
 31 | 
 32 | // Trip duration expressions
 33 | export interface Duration {
 34 |   type: 'days' | 'weeks' | 'months' | 'flexible';
 35 |   value: number | [number, number];  // Single value or range
 36 |   isApproximate: boolean;
 37 | }
 38 | 
 39 | // Location reference in query
 40 | export interface LocationReference {
 41 |   raw: string;                    // Original text
 42 |   type: 'city' | 'airport' | 'region' | 'country';
 43 |   code?: string;                  // Airport/city code if known
 44 |   isFlexible: boolean;           // Whether location is flexible ("somewhere warm")
 45 |   context?: string;              // Additional context ("beach cities", "major cities")
 46 | }
 47 | 
 48 | // Budget information
 49 | export interface BudgetConstraint {
 50 |   amount: number;
 51 |   currency: string;
 52 |   type: 'total' | 'per_person' | 'per_flight';
 53 |   isFlexible: boolean;
 54 |   context?: string;              // "cheap", "luxury", "best value"
 55 | }
 56 | 
 57 | // Travel preferences
 58 | export interface TravelPreferences {
 59 |   purpose?: 'leisure' | 'business' | 'family' | 'adventure';
 60 |   class?: 'economy' | 'premium_economy' | 'business' | 'first';
 61 |   stops?: 'direct' | 'any' | number;
 62 |   activities?: string[];         // "beach", "skiing", "sightseeing"
 63 |   accommodation?: string[];      // "resort", "hotel", "any"
 64 | }
 65 | 
 66 | // The main structure for analyzed queries
 67 | export interface AnalyzedQuery {
 68 |   type: TravelQueryType;
 69 |   timeFrame: {
 70 |     type: TimeFrame;
 71 |     value: string | [string, string];  // Single date or range
 72 |     isFlexible: boolean;
 73 |   };
 74 |   origin?: LocationReference;
 75 |   destinations: LocationReference[];
 76 |   duration?: Duration;
 77 |   budget?: BudgetConstraint;
 78 |   climate?: ClimatePreference;
 79 |   preferences?: TravelPreferences;
 80 |   rawQuery: string;              // Original query text
 81 |   confidence: number;            // Confidence in the analysis (0-1)
 82 |   ambiguities?: string[];       // List of unclear aspects
 83 | }
 84 | 
 85 | // Zod schema for validation
 86 | export const analyzedQuerySchema = z.object({
 87 |   type: z.nativeEnum(TravelQueryType),
 88 |   timeFrame: z.object({
 89 |     type: z.nativeEnum(TimeFrame),
 90 |     value: z.union([z.string(), z.tuple([z.string(), z.string()])]),
 91 |     isFlexible: z.boolean(),
 92 |   }),
 93 |   origin: z.object({
 94 |     raw: z.string(),
 95 |     type: z.enum(['city', 'airport', 'region', 'country']),
 96 |     code: z.string().optional(),
 97 |     isFlexible: z.boolean(),
 98 |     context: z.string().optional(),
 99 |   }).optional(),
100 |   destinations: z.array(z.object({
101 |     raw: z.string(),
102 |     type: z.enum(['city', 'airport', 'region', 'country']),
103 |     code: z.string().optional(),
104 |     isFlexible: z.boolean(),
105 |     context: z.string().optional(),
106 |   })),
107 |   duration: z.object({
108 |     type: z.enum(['days', 'weeks', 'months', 'flexible']),
109 |     value: z.union([z.number(), z.tuple([z.number(), z.number()])]),
110 |     isApproximate: z.boolean(),
111 |   }).optional(),
112 |   budget: z.object({
113 |     amount: z.number(),
114 |     currency: z.string(),
115 |     type: z.enum(['total', 'per_person', 'per_flight']),
116 |     isFlexible: z.boolean(),
117 |     context: z.string().optional(),
118 |   }).optional(),
119 |   climate: z.nativeEnum(ClimatePreference).optional(),
120 |   preferences: z.object({
121 |     purpose: z.enum(['leisure', 'business', 'family', 'adventure']).optional(),
122 |     class: z.enum(['economy', 'premium_economy', 'business', 'first']).optional(),
123 |     stops: z.union([z.enum(['direct', 'any']), z.number()]).optional(),
124 |     activities: z.array(z.string()).optional(),
125 |     accommodation: z.array(z.string()).optional(),
126 |   }).optional(),
127 |   rawQuery: z.string(),
128 |   confidence: z.number().min(0).max(1),
129 |   ambiguities: z.array(z.string()).optional(),
130 | });
131 | 
132 | // Helper function to identify query type
133 | export function identifyQueryType(query: string): TravelQueryType {
134 |   const lowercaseQuery = query.toLowerCase();
135 |   
136 |   // Inspiration patterns
137 |   if (lowercaseQuery.includes('where can i go') ||
138 |       lowercaseQuery.includes('suggest') ||
139 |       lowercaseQuery.includes('recommend')) {
140 |     return TravelQueryType.INSPIRATION;
141 |   }
142 |   
143 |   // Multi-city patterns
144 |   if (lowercaseQuery.includes('visit') ||
145 |       lowercaseQuery.includes('multiple cities') ||
146 |       (lowercaseQuery.match(/,/g) || []).length >= 2) {
147 |     return TravelQueryType.MULTI_CITY;
148 |   }
149 |   
150 |   // Flexible value patterns
151 |   if (lowercaseQuery.includes('cheapest time') ||
152 |       lowercaseQuery.includes('best time') ||
153 |       lowercaseQuery.includes('when should')) {
154 |     return TravelQueryType.FLEXIBLE_VALUE;
155 |   }
156 |   
157 |   // Default to specific route
158 |   return TravelQueryType.SPECIFIC_ROUTE;
159 | }
160 | 
161 | // Helper function to extract time frame
162 | export function extractTimeFrame(query: string): {
163 |   type: TimeFrame;
164 |   value: string | [string, string];
165 |   isFlexible: boolean;
166 | } {
167 |   // This is a placeholder - would need more sophisticated date parsing
168 |   return {
169 |     type: TimeFrame.FLEXIBLE,
170 |     value: '',
171 |     isFlexible: true,
172 |   };
173 | }
174 | 
175 | // Helper function to extract locations
176 | export function extractLocations(query: string): {
177 |   origin?: LocationReference;
178 |   destinations: LocationReference[];
179 | } {
180 |   // This is a placeholder - would need location database and parsing
181 |   return {
182 |     destinations: [],
183 |   };
184 | }
185 | 
186 | // Main analysis function
187 | export async function analyzeQuery(query: string): Promise<AnalyzedQuery> {
188 |   const type = identifyQueryType(query);
189 |   const timeFrame = extractTimeFrame(query);
190 |   const locations = extractLocations(query);
191 |   
192 |   const analysis: AnalyzedQuery = {
193 |     type,
194 |     timeFrame,
195 |     ...locations,
196 |     rawQuery: query,
197 |     confidence: 0.8, // This should be calculated based on certainty of parsing
198 |   };
199 |   
200 |   // Validate the analysis
201 |   analyzedQuerySchema.parse(analysis);
202 |   
203 |   return analysis;
204 | } 
```

--------------------------------------------------------------------------------
/src/prompt.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | // Prompt for analyzing flight prices
  3 | import { server } from './index.js';
  4 | 
  5 | server.prompt(
  6 |   'analyze-flight-prices',
  7 |   'Analyze flight prices for a route',
  8 |   {
  9 |     originLocationCode: z
 10 |       .string()
 11 |       .length(3)
 12 |       .describe('Origin airport IATA code (e.g., JFK)'),
 13 |     destinationLocationCode: z
 14 |       .string()
 15 |       .length(3)
 16 |       .describe('Destination airport IATA code (e.g., LHR)'),
 17 |     departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
 18 |     returnDate: z
 19 |       .string()
 20 |       .optional()
 21 |       .describe('Return date in YYYY-MM-DD format (for round trips)'),
 22 |   },
 23 |   async ({
 24 |     originLocationCode,
 25 |     destinationLocationCode,
 26 |     departureDate,
 27 |     returnDate,
 28 |   }) => {
 29 |     return {
 30 |       messages: [
 31 |         {
 32 |           role: 'user',
 33 |           content: {
 34 |             type: 'text',
 35 |             text: `Please analyze flight prices for a trip from ${originLocationCode} to ${destinationLocationCode} departing on ${departureDate}${
 36 |               returnDate ? ` and returning on ${returnDate}` : ''
 37 |             }. 
 38 | 
 39 | Please use the flight-price-analysis tool to get price metrics, and the search-flights tool to find actual flight options. Then provide:
 40 | 
 41 | 1. An overview of the price range
 42 | 2. Insights on whether current prices are high or low compared to average
 43 | 3. Recommendations on when to book
 44 | 4. A few specific flight options that offer good value
 45 | 5. Any additional insights that would be helpful for a traveler
 46 | 
 47 | If you need to search for airport information, you can use the search-airports tool.`,
 48 |           },
 49 |         },
 50 |       ],
 51 |     };
 52 |   },
 53 | );
 54 | 
 55 | // Prompt for finding the best flight deals
 56 | server.prompt(
 57 |   'find-best-deals',
 58 |   'Find the best flight deals',
 59 |   {
 60 |     originLocationCode: z
 61 |       .string()
 62 |       .length(3)
 63 |       .describe('Origin airport IATA code (e.g., JFK)'),
 64 |     destinationLocationCode: z
 65 |       .string()
 66 |       .length(3)
 67 |       .describe('Destination airport IATA code (e.g., LHR)'),
 68 |     departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
 69 |     returnDate: z
 70 |       .string()
 71 |       .optional()
 72 |       .describe('Return date in YYYY-MM-DD format (for round trips)'),
 73 |     travelClass: z
 74 |       .enum(['ECONOMY', 'PREMIUM_ECONOMY', 'BUSINESS', 'FIRST'])
 75 |       .optional()
 76 |       .describe('Travel class'),
 77 |   },
 78 |   async ({
 79 |     originLocationCode,
 80 |     destinationLocationCode,
 81 |     departureDate,
 82 |     returnDate,
 83 |     travelClass,
 84 |   }) => {
 85 |     return {
 86 |       messages: [
 87 |         {
 88 |           role: 'user',
 89 |           content: {
 90 |             type: 'text',
 91 |             text: `Please find the best flight deals for a trip from ${originLocationCode} to ${destinationLocationCode} departing on ${departureDate}${
 92 |               returnDate ? ` and returning on ${returnDate}` : ''
 93 |             }${travelClass ? ` in ${travelClass} class` : ''}.
 94 | 
 95 | Please use the search-flights tool to find options, and organize them by:
 96 | 
 97 | 1. Best value options (considering price, duration, and convenience)
 98 | 2. Cheapest options regardless of convenience
 99 | 3. Most convenient options (fewest stops, best times)
100 | 
101 | For each option, provide a brief summary of why it might be a good choice for different types of travelers.`,
102 |           },
103 |         },
104 |       ],
105 |     };
106 |   },
107 | );
108 | 
109 | // Prompt for planning a multi-city trip
110 | server.prompt(
111 |   'plan-multi-city-trip',
112 |   'Plan a multi-city trip',
113 |   {
114 |     cities: z
115 |       .string()
116 |       .describe('Comma-separated list of city or airport codes to visit'),
117 |     startDate: z.string().describe('Start date of trip in YYYY-MM-DD format'),
118 |     endDate: z.string().describe('End date of trip in YYYY-MM-DD format'),
119 |     homeAirport: z.string().length(3).describe('Home airport IATA code'),
120 |   },
121 |   async ({ cities, startDate, endDate, homeAirport }) => {
122 |     return {
123 |       messages: [
124 |         {
125 |           role: 'user',
126 |           content: {
127 |             type: 'text',
128 |             text: `Please help me plan a multi-city trip visiting the following cities: ${cities}. I'll be starting from ${homeAirport} on ${startDate} and returning on ${endDate}.
129 | 
130 | Please use the search-airports tool to confirm airport codes for each city, and then use the search-flights tool to find optimal flight routes between each city.
131 | 
132 | For my trip plan, I would like:
133 | 
134 | 1. The most logical order to visit these cities to minimize backtracking
135 | 2. Flight options between each city
136 | 3. Recommended number of days in each location based on the total trip duration
137 | 4. Any insights about potential challenges or considerations for this itinerary
138 | 
139 | Please outline a complete trip plan with flight details and suggested stays in each location.`,
140 |           },
141 |         },
142 |       ],
143 |     };
144 |   },
145 | );
146 | 
147 | // Prompt for finding cheapest dates to travel
148 | server.prompt(
149 |   'find-cheapest-travel-dates',
150 |   'Find the cheapest dates to travel for a given route',
151 |   {
152 |     originLocationCode: z
153 |       .string()
154 |       .length(3)
155 |       .describe('Origin airport IATA code (e.g., JFK)'),
156 |     destinationLocationCode: z
157 |       .string()
158 |       .length(3)
159 |       .describe('Destination airport IATA code (e.g., LHR)'),
160 |     earliestDepartureDate: z
161 |       .string()
162 |       .describe('Earliest possible departure date in YYYY-MM-DD format'),
163 |     latestDepartureDate: z
164 |       .string()
165 |       .describe('Latest possible departure date in YYYY-MM-DD format'),
166 |     tripDuration: z
167 |       .string()
168 |       .optional()
169 |       .describe('Desired trip duration in days (for round trips)'),
170 |   },
171 |   async ({
172 |     originLocationCode,
173 |     destinationLocationCode,
174 |     earliestDepartureDate,
175 |     latestDepartureDate,
176 |     tripDuration,
177 |   }) => {
178 |     return {
179 |       messages: [
180 |         {
181 |           role: 'user',
182 |           content: {
183 |             type: 'text',
184 |             text: `I'm looking for the cheapest dates to fly from ${originLocationCode} to ${destinationLocationCode} between ${earliestDepartureDate} and ${latestDepartureDate}${
185 |               tripDuration
186 |                 ? ` for a trip duration of approximately ${tripDuration} days`
187 |                 : ''
188 |             }.
189 | 
190 | Please use the find-cheapest-dates tool to identify the most economical travel dates, and then provide:
191 | 
192 | 1. A list of the cheapest date combinations
193 | 2. An analysis of price trends during this period
194 | 3. Recommendations on the best days of the week to travel for this route
195 | 4. Any holidays or events that might be affecting pricing
196 | 5. Specific flight options for the cheapest dates found
197 | 
198 | Please organize this information clearly to help me make an informed decision about when to book my trip.`,
199 |           },
200 |         },
201 |       ],
202 |     };
203 |   },
204 | );
205 | 
206 | // Prompt for discovering flight destinations
207 | server.prompt(
208 |   'discover-destinations',
209 |   'Find inspiring flight destinations within your budget',
210 |   {
211 |     originLocationCode: z
212 |       .string()
213 |       .length(3)
214 |       .describe('Origin airport IATA code (e.g., MAD)'),
215 |     maxPrice: z
216 |       .string()
217 |       .optional()
218 |       .describe('Maximum budget for flights'),
219 |     departureDate: z
220 |       .string()
221 |       .optional()
222 |       .describe('Preferred departure date or date range (YYYY-MM-DD)'),
223 |     tripDuration: z
224 |       .string()
225 |       .optional()
226 |       .describe('Desired trip duration in days (e.g., "7" or "2,8" for range)'),
227 |   },
228 |   async ({ originLocationCode, maxPrice, departureDate, tripDuration }) => {
229 |     return {
230 |       messages: [
231 |         {
232 |           role: 'user',
233 |           content: {
234 |             type: 'text',
235 |             text: `Please help me discover interesting destinations I can fly to from ${originLocationCode}${
236 |               maxPrice ? ` within a budget of ${maxPrice}` : ''
237 |             }${departureDate ? ` around ${departureDate}` : ''}${
238 |               tripDuration ? ` for about ${tripDuration} days` : ''
239 |             }.
240 | 
241 | Please use the flight-inspiration tool to find destinations and then:
242 | 
243 | 1. Group destinations by region or country
244 | 2. Highlight the best deals found
245 | 3. Provide insights about seasonal trends
246 | 4. Suggest specific destinations that offer good value
247 | 5. Include any interesting destinations that might be unexpected
248 | 
249 | For the most interesting options, please use the search-flights tool to find specific flight details.
250 | If needed, use the search-airports tool to get more information about the destinations.
251 | 
252 | Please organize the results to help me discover new travel possibilities within my constraints.`,
253 |           },
254 |         },
255 |       ],
256 |     };
257 |   },
258 | );
259 | 
260 | // Prompt for exploring airport routes
261 | server.prompt(
262 |   'explore-airport-routes',
263 |   'Discover direct routes and connections from an airport',
264 |   {
265 |     airportCode: z
266 |       .string()
267 |       .length(3)
268 |       .describe('Airport IATA code (e.g., JFK)'),
269 |     maxResults: z
270 |       .string()
271 |       .optional()
272 |       .default("20")
273 |       .describe('Maximum number of routes to show'),
274 |   },
275 |   async ({ airportCode, maxResults }) => {
276 |     return {
277 |       messages: [
278 |         {
279 |           role: 'user',
280 |           content: {
281 |             type: 'text',
282 |             text: `Please analyze the routes available from ${airportCode} airport.
283 | 
284 | Please use the airport-routes tool to find direct destinations, and then:
285 | 
286 | 1. Group destinations by region/continent
287 | 2. Highlight major routes with high flight frequency
288 | 3. Identify popular leisure and business destinations
289 | 4. List any seasonal or unique routes
290 | 5. Provide insights about the airport's connectivity
291 | 
292 | For key routes, please use the search-flights tool to check typical prices and schedules.
293 | Use the search-airports tool to get more details about the connected airports.
294 | 
295 | Please organize this information to help understand:
296 | - The airport's route network
297 | - Best connection possibilities
298 | - Popular destinations served
299 | - Unique route opportunities`,
300 |           },
301 |         },
302 |       ],
303 |     };
304 |   },
305 | );
306 | 
307 | // Prompt for finding nearby airports
308 | server.prompt(
309 |   'find-nearby-airports',
310 |   'Find convenient airports near a specific location',
311 |   {
312 |     latitude: z.string().describe('Location latitude'),
313 |     longitude: z.string().describe('Location longitude'),
314 |     radius: z
315 |       .string()
316 |       .optional()
317 |       .default("500")
318 |       .describe('Search radius in kilometers'),
319 |     maxResults: z
320 |       .string()
321 |       .optional()
322 |       .default("10")
323 |       .describe('Maximum number of airports to show'),
324 |   },
325 |   async ({ latitude, longitude, radius, maxResults }) => {
326 |     return {
327 |       messages: [
328 |         {
329 |           role: 'user',
330 |           content: {
331 |             type: 'text',
332 |             text: `Please help me find convenient airports near latitude ${latitude}, longitude ${longitude}${
333 |               radius ? ` within ${radius} kilometers` : ''
334 |             }.
335 | 
336 | Please use the nearest-airports tool to find airports, and then:
337 | 
338 | 1. Rank airports by convenience (considering distance and flight options)
339 | 2. Provide key information about each airport (size, typical destinations)
340 | 3. Compare transportation options to/from each airport
341 | 4. Highlight any airports with unique advantages
342 | 5. Suggest which airports might be best for different types of trips
343 | 
344 | For the most relevant airports:
345 | - Use the airport-routes tool to check available destinations
346 | - Use the search-flights tool to compare typical prices
347 | - Consider factors like flight frequency and seasonal variations
348 | 
349 | Please organize this information to help choose the most suitable airport based on:
350 | - Distance and accessibility
351 | - Flight options and frequencies
352 | - Typical prices
353 | - Overall convenience for different types of travel`,
354 |           },
355 |         },
356 |       ],
357 |     };
358 |   },
359 | );
360 | 
361 | // Prompt for comprehensive trip planning
362 | server.prompt(
363 |   'plan-complete-trip',
364 |   'Get comprehensive trip planning assistance',
365 |   {
366 |     originLocationCode: z
367 |       .string()
368 |       .length(3)
369 |       .describe('Origin airport IATA code'),
370 |     budget: z.string().optional().describe('Total budget for flights'),
371 |     departureDate: z
372 |       .string()
373 |       .optional()
374 |       .describe('Preferred departure date or date range'),
375 |     tripDuration: z
376 |       .string()
377 |       .optional()
378 |       .describe('Desired trip duration in days'),
379 |     preferences: z
380 |       .string()
381 |       .optional()
382 |       .describe('Travel preferences (e.g., "beach, culture, food")'),
383 |   },
384 |   async ({
385 |     originLocationCode,
386 |     budget,
387 |     departureDate,
388 |     tripDuration,
389 |     preferences,
390 |   }) => {
391 |     return {
392 |       messages: [
393 |         {
394 |           role: 'user',
395 |           content: {
396 |             type: 'text',
397 |             text: `Please help me plan a trip from ${originLocationCode}${
398 |               budget ? ` with a budget of ${budget}` : ''
399 |             }${departureDate ? ` around ${departureDate}` : ''}${
400 |               tripDuration ? ` for ${tripDuration} days` : ''
401 |             }${preferences ? ` focusing on ${preferences}` : ''}.
402 | 
403 | Please use multiple tools to create a comprehensive trip plan:
404 | 
405 | 1. Use the flight-inspiration tool to discover potential destinations that match my criteria
406 | 2. Use the nearest-airports tool to find alternative departure/arrival airports
407 | 3. Use the airport-routes tool to understand connection possibilities
408 | 4. Use the find-cheapest-dates tool to optimize travel dates
409 | 5. Use the search-flights tool to find specific flight options
410 | 
411 | Please provide:
412 | 1. Top destination recommendations based on my criteria
413 | 2. Best flight options and routing suggestions
414 | 3. Price analysis and booking timing recommendations
415 | 4. Alternative airports to consider
416 | 5. A complete trip outline with:
417 |    - Recommended destinations
418 |    - Flight options and prices
419 |    - Suggested itinerary
420 |    - Travel tips and considerations
421 | 
422 | Please organize all this information into a clear, actionable trip plan.`,
423 |           },
424 |         },
425 |       ],
426 |     };
427 |   },
428 | );
429 | 
```

--------------------------------------------------------------------------------
/api-spectifications/AirportRoutes_v1_Version_1.1_swagger_specification.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "openapi": "3.0.0",
  3 |   "info": {
  4 |     "title": "Airport Routes",
  5 |     "version": "1.1.0",
  6 |     "description": "Before using the API you will need to get an access token. Please read our **[Authorization Guide](https://developers.amadeus.com/self-service/apis-docs/guides/authorization)** for more information on how to get your token."
  7 |   },
  8 |   "servers": [
  9 |     {
 10 |       "url": "https://test.api.amadeus.com/v1"
 11 |     }
 12 |   ],
 13 |   "paths": {
 14 |     "/airport/direct-destinations": {
 15 |       "get": {
 16 |         "summary": "get airport direct routes",
 17 |         "tags": [
 18 |           "direct-destinations"
 19 |         ],
 20 |         "responses": {
 21 |           "200": {
 22 |             "description": "Success Response",
 23 |             "content": {
 24 |               "application/vnd.amadeus+json": {
 25 |                 "schema": {
 26 |                   "type": "object",
 27 |                   "properties": {
 28 |                     "warnings": {
 29 |                       "type": "array",
 30 |                       "items": {
 31 |                         "$ref": "#/components/schemas/warnings"
 32 |                       }
 33 |                     },
 34 |                     "data": {
 35 |                       "type": "array",
 36 |                       "items": {
 37 |                         "$ref": "#/components/schemas/locations"
 38 |                       }
 39 |                     },
 40 |                     "meta": {
 41 |                       "$ref": "#/components/schemas/meta"
 42 |                     }
 43 |                   }
 44 |                 },
 45 |                 "examples": {
 46 |                   "Successful Reply": {
 47 |                     "value": {
 48 |                       "meta": {
 49 |                         "count": 3,
 50 |                         "links": {
 51 |                           "self": "https://api.amadeus.com/v1/airport/direct-destinations?departureAirportCode=ORY&max=3&arrivalCountryCode=FR"
 52 |                         }
 53 |                       },
 54 |                       "data": [
 55 |                         {
 56 |                           "type": "location",
 57 |                           "subtype": "city",
 58 |                           "name": "TOULOUSE",
 59 |                           "iataCode": "TLS",
 60 |                           "geoCode": {
 61 |                             "latitude": 43.62908,
 62 |                             "longitude": 1.36382
 63 |                           },
 64 |                           "address": {
 65 |                             "cityName": "TOULOUSE",
 66 |                             "countryName": "FRANCE",
 67 |                             "stateCode": "FR-31",
 68 |                             "regionCode": "EUROP"
 69 |                           },
 70 |                           "timeZone": {
 71 |                             "offset": "+02:00",
 72 |                             "referenceLocalDateTime": "2022-10-25T10:17:00"
 73 |                           },
 74 |                           "metrics": {
 75 |                             "relevance": 100
 76 |                           }
 77 |                         },
 78 |                         {
 79 |                           "type": "location",
 80 |                           "subtype": "city",
 81 |                           "name": "NICE",
 82 |                           "iataCode": "NCE",
 83 |                           "geoCode": {
 84 |                             "latitude": 43.66272,
 85 |                             "longitude": 7.20787
 86 |                           },
 87 |                           "address": {
 88 |                             "cityName": "NICE",
 89 |                             "countryName": "FRANCE",
 90 |                             "stateCode": "FR-06",
 91 |                             "regionCode": "EUROP"
 92 |                           },
 93 |                           "timeZone": {
 94 |                             "offset": "+02:00",
 95 |                             "referenceLocalDateTime": "2022-10-25T10:17:00"
 96 |                           },
 97 |                           "metrics": {
 98 |                             "relevance": 97
 99 |                           }
100 |                         },
101 |                         {
102 |                           "type": "location",
103 |                           "subtype": "city",
104 |                           "name": "MARSEILLE",
105 |                           "iataCode": "MRS",
106 |                           "geoCode": {
107 |                             "latitude": 43.43556,
108 |                             "longitude": 5.21361
109 |                           },
110 |                           "address": {
111 |                             "cityName": "MARSEILLE",
112 |                             "countryName": "FRANCE",
113 |                             "stateCode": "FR-06",
114 |                             "regionCode": "EUROP"
115 |                           },
116 |                           "timeZone": {
117 |                             "offset": "+02:00",
118 |                             "referenceLocalDateTime": "2022-10-25T10:17:00"
119 |                           },
120 |                           "metrics": {
121 |                             "relevance": 45
122 |                           }
123 |                         }
124 |                       ]
125 |                     }
126 |                   }
127 |                 }
128 |               }
129 |             }
130 |           },
131 |           "400": {
132 |             "description": "Bad Request\n\ncode    | title                                 \n------- | ------------------------------------- \n572     | INVALID OPTION                         \n32171   | MANDATORY DATA MISSING \n477     | INVALID FORMAT                        ",
133 |             "content": {
134 |               "application/vnd.amadeus+json": {
135 |                 "schema": {
136 |                   "type": "object",
137 |                   "properties": {
138 |                     "errors": {
139 |                       "type": "array",
140 |                       "items": {
141 |                         "$ref": "#/components/schemas/errors"
142 |                       }
143 |                     }
144 |                   }
145 |                 },
146 |                 "examples": {
147 |                   "example-error 400": {
148 |                     "value": {
149 |                       "errors": [
150 |                         {
151 |                           "status": 400,
152 |                           "code": 32171,
153 |                           "title": "MANDATORY DATA MISSING",
154 |                           "detail": "Missing mandatory query parameter 'departureAirportCode"
155 |                         }
156 |                       ]
157 |                     }
158 |                   }
159 |                 }
160 |               }
161 |             }
162 |           },
163 |           "500": {
164 |             "description": "Internal Server Error\n\ncode    | title                                 \n------- | ------------------------------------- \n141     | SYSTEM ERROR HAS OCCURRED",
165 |             "content": {
166 |               "application/vnd.amadeus+json": {
167 |                 "schema": {
168 |                   "type": "object",
169 |                   "properties": {
170 |                     "errors": {
171 |                       "type": "array",
172 |                       "items": {
173 |                         "$ref": "#/components/schemas/errors"
174 |                       }
175 |                     }
176 |                   }
177 |                 },
178 |                 "examples": {
179 |                   "example-error 500": {
180 |                     "value": {
181 |                       "errors": [
182 |                         {
183 |                           "status": 500,
184 |                           "code": 141,
185 |                           "title": "SYSTEM ERROR HAS OCCURRED"
186 |                         }
187 |                       ]
188 |                     }
189 |                   }
190 |                 }
191 |               }
192 |             }
193 |           }
194 |         },
195 |         "operationId": "airport/direct-destinations",
196 |         "parameters": [
197 |           {
198 |             "schema": {
199 |               "type": "string"
200 |             },
201 |             "in": "query",
202 |             "name": "departureAirportCode",
203 |             "description": "Departure Airport code following [IATA standard](http://www.iata.org/publications/Pages/code-search.aspx)",
204 |             "required": true,
205 |             "example": "BLR"
206 |           },
207 |           {
208 |             "schema": {
209 |               "type": "integer"
210 |             },
211 |             "in": "query",
212 |             "name": "max",
213 |             "description": "Maximum number of destination in the response."
214 |           },
215 |           {
216 |             "schema": {
217 |               "type": "string"
218 |             },
219 |             "in": "query",
220 |             "name": "arrivalCountryCode",
221 |             "description": "Arrival country code following [IATA standard](http://www.iata.org/publications/Pages/code-search.aspx), to filter the list of destinations"
222 |           }
223 |         ],
224 |         "description": ""
225 |       }
226 |     }
227 |   },
228 |   "components": {
229 |     "schemas": {
230 |       "locations": {
231 |         "title": "destination",
232 |         "type": "object",
233 |         "description": "Description of a particular point or place in physical space",
234 |         "properties": {
235 |           "type": {
236 |             "type": "string",
237 |             "description": "type of API result \"location\""
238 |           },
239 |           "subtype": {
240 |             "type": "string",
241 |             "description": "Location sub-type (e.g. airport, port, rail-station, restaurant, atm...)"
242 |           },
243 |           "name": {
244 |             "type": "string",
245 |             "description": "Label associated to the location (e.g. Eiffel Tower, Madison Square)",
246 |             "example": "\"Eiffel Tower\""
247 |           },
248 |           "iataCode": {
249 |             "type": "string",
250 |             "description": "IATA location code",
251 |             "example": "\"PAR\""
252 |           },
253 |           "geoCode": {
254 |             "type": "object",
255 |             "description": "Geographic coordinates describing the position of any location on the surface of Earth",
256 |             "properties": {
257 |               "latitude": {
258 |                 "type": "number",
259 |                 "description": "Latitude of the position expressed in decimal degrees (WSG 84), e.g. 6.244203. A positive value denotes northern hemisphere or the equator, and a negative value denotes southern hemisphere. The number of digits to represent the precision of the coordinate.",
260 |                 "example": "48.85837",
261 |                 "minimum": -3.402823669209385e+38,
262 |                 "multipleOf": 3.402823669209385e+38
263 |               },
264 |               "longitude": {
265 |                 "type": "number",
266 |                 "description": "Longitude of the position expressed in decimal degrees (WSG 84), e.g. -75.581211. A positive value denotes east longitude or the prime meridian, and a negative value denotes west longitude. The number of digits to represent the precision of the coordinate.",
267 |                 "example": "2.294481",
268 |                 "minimum": -3.402823669209385e+38,
269 |                 "multipleOf": 3.402823669209385e+38
270 |               }
271 |             }
272 |           },
273 |           "address": {
274 |             "type": "object",
275 |             "properties": {
276 |               "countryName": {
277 |                 "type": "string",
278 |                 "description": "Name of the country of the location",
279 |                 "example": "France"
280 |               },
281 |               "countryCode": {
282 |                 "type": "string",
283 |                 "description": "Code of the country of the location in ISO standard",
284 |                 "example": "FR"
285 |               },
286 |               "stateCode": {
287 |                 "type": "string",
288 |                 "description": "Code of the state of the location (if any)",
289 |                 "example": "FR-13"
290 |               },
291 |               "regionCode": {
292 |                 "type": "string",
293 |                 "description": "Code of the region of the location in ISO standard",
294 |                 "example": "EUROP"
295 |               }
296 |             }
297 |           },
298 |           "timeZone": {
299 |             "type": "object",
300 |             "properties": {
301 |               "offSet": {
302 |                 "type": "string",
303 |                 "description": "'Total offset from UTC including the Daylight Saving Time (DST) following ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601) standard'",
304 |                 "example": "+01:00"
305 |               },
306 |               "referenceLocalDateTime": {
307 |                 "type": "string",
308 |                 "description": "Date and time used as reference to determine the time zone name, code, offset, and dstOffset following ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601) standard.",
309 |                 "example": "2022-09-28T19:20:30"
310 |               }
311 |             }
312 |           },
313 |           "metrics": {
314 |             "type": "object",
315 |             "properties": {
316 |               "relevance": {
317 |                 "description": "Score value based on the number of travelers per year and per destination. Score is between 0 and 100, 100 being the value for the destination city with the highest value of travelers for the origin airport",
318 |                 "example": "100",
319 |                 "type": "integer"
320 |               }
321 |             }
322 |           }
323 |         }
324 |       },
325 |       "meta": {
326 |         "title": "meta",
327 |         "type": "object",
328 |         "description": "Meta information about the returned object(s) in \"data\"",
329 |         "properties": {
330 |           "count": {
331 |             "type": "integer",
332 |             "description": "Total number of object(s) retrieved",
333 |             "format": "int64"
334 |           },
335 |           "links": {
336 |             "type": "object",
337 |             "description": "Links related to the returned object(s)",
338 |             "properties": {
339 |               "self": {
340 |                 "type": "string",
341 |                 "description": "Link to the same page.",
342 |                 "format": "uri"
343 |               }
344 |             }
345 |           }
346 |         }
347 |       },
348 |       "warnings": {
349 |         "title": "warning",
350 |         "type": "object",
351 |         "properties": {
352 |           "code": {
353 |             "type": "integer",
354 |             "description": "A machine-readable error code from the Canned Messages table, that will enable the API Consumers code to handle this type of error"
355 |           },
356 |           "title": {
357 |             "type": "string",
358 |             "description": "An error title from the Canned Messages table with a 1:1 correspondence to the error code. This may be localized"
359 |           },
360 |           "detail": {
361 |             "type": "string",
362 |             "description": "An easy-to-read explanation specific to this occurrence of the problem. It should give the API consumer an idea of what went wrong and how to recover from it. Like the title, this field’s value can be localized."
363 |           },
364 |           "source": {
365 |             "type": "object",
366 |             "description": "The Warning Source Definition",
367 |             "properties": {
368 |               "parameter": {
369 |                 "type": "string",
370 |                 "description": "The key of the URI path or query parameter that caused the error"
371 |               },
372 |               "pointer": {
373 |                 "type": "string",
374 |                 "description": "A JSON Pointer RFC6901 to the associated entity in the request body that caused this error"
375 |               },
376 |               "example": {
377 |                 "type": "string",
378 |                 "description": "A sample input to guide the user when resolving this issue"
379 |               }
380 |             }
381 |           }
382 |         }
383 |       },
384 |       "errors": {
385 |         "title": "Error",
386 |         "properties": {
387 |           "status": {
388 |             "type": "integer",
389 |             "description": "The [HTTP status code](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) of this response. This is present only in terminal errors which cause an unsuccessful response. In the case of multiple errors, they must all have the same status."
390 |           },
391 |           "code": {
392 |             "type": "integer",
393 |             "description": "A machine-readable error code from the Amadeus Canned Messages table, that will enable the API Consumers code to handle this type of error"
394 |           },
395 |           "title": {
396 |             "type": "string",
397 |             "description": "An error title from the Canned Messages table with a 1:1 correspondence to the error code. This may be localized"
398 |           },
399 |           "detail": {
400 |             "type": "string",
401 |             "description": "An easy-to-read explanation specific to this occurrence of the problem. It should give the API consumer an idea of what went wrong and how to recover from it. Like the title, this field’s value can be localized."
402 |           },
403 |           "source": {
404 |             "type": "object",
405 |             "title": "Error_Source",
406 |             "properties": {
407 |               "parameter": {
408 |                 "type": "string",
409 |                 "description": "The key of the URI path or query parameter that caused the error"
410 |               },
411 |               "pointer": {
412 |                 "type": "string",
413 |                 "description": "A JSON Pointer [RFC6901] to the associated entity in the request body that caused this error"
414 |               },
415 |               "example": {
416 |                 "type": "string",
417 |                 "description": "A sample input to guide the user when resolving this issue"
418 |               }
419 |             }
420 |           }
421 |         }
422 |       }
423 |     }
424 |   }
425 | }
```

--------------------------------------------------------------------------------
/api-spectifications/AirportCitySearch_v1_Version_1.0_swagger_specification.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "swagger": "2.0",
  3 |   "info": {
  4 |     "version": "1.2.3",
  5 |     "title": "Airport & City Search",
  6 |     "x-status": "validated",
  7 |     "x-tags": [
  8 |       "#ama-for-dev"
  9 |     ],
 10 |     "x-release-note": {
 11 |       "1.2": [
 12 |         "Remove parameter onlyMajor",
 13 |         "Correct example"
 14 |       ],
 15 |       "1.1": [
 16 |         "AFD-1091 - change from \"traveller\" to \"traveler\"",
 17 |         "change default value of view indicator to FULL",
 18 |         "Change search algorithm",
 19 |         "Addition of \"id\" for location",
 20 |         "New operation GET Airport or City by id",
 21 |         "Traveler score become interger (PTR 14827552)",
 22 |         "Change the option parameter into view and onlyMajor parameter",
 23 |         "add a characters restriction on keyword parameter"
 24 |       ],
 25 |       "1.0": [
 26 |         "Initial Version"
 27 |       ]
 28 |     },
 29 |     "description": "\nBefore using this API, we recommend you read our **[Authorization Guide](https://developers.amadeus.com/self-service/apis-docs/guides/authorization-262)** for more information on how to generate an access token. \n\nPlease also be aware that our test environment is based on a subset of the production, in test this API only contains data from the United States, Spain, United Kingdom, Germany and India. "
 30 |   },
 31 |   "host": "test.api.amadeus.com",
 32 |   "basePath": "/v1",
 33 |   "schemes": [
 34 |     "https"
 35 |   ],
 36 |   "consumes": [
 37 |     "application/vnd.amadeus+json"
 38 |   ],
 39 |   "produces": [
 40 |     "application/vnd.amadeus+json"
 41 |   ],
 42 |   "paths": {
 43 |     "/reference-data/locations": {
 44 |       "get": {
 45 |         "tags": [
 46 |           "location"
 47 |         ],
 48 |         "operationId": "getAirportCitySearch",
 49 |         "summary": "Returns a list of airports and cities matching a given keyword.",
 50 |         "parameters": [
 51 |           {
 52 |             "name": "subType",
 53 |             "description": "sub type of the location (AIRPORT and/or CITY)",
 54 |             "in": "query",
 55 |             "required": true,
 56 |             "type": "array",
 57 |             "items": {
 58 |               "type": "string",
 59 |               "enum": [
 60 |                 "AIRPORT",
 61 |                 "CITY"
 62 |               ]
 63 |             },
 64 |             "collectionFormat": "csv",
 65 |             "x-example": "CITY"
 66 |           },
 67 |           {
 68 |             "name": "keyword",
 69 |             "description": "keyword that should represent the start of a word in a city or airport name or code. \n Supported charaters are: A-Za-z0-9./:-'()\"",
 70 |             "in": "query",
 71 |             "required": true,
 72 |             "type": "string",
 73 |             "x-example": "MUC",
 74 |             "pattern": "[A-Za-z0-9./:()'\"-]"
 75 |           },
 76 |           {
 77 |             "name": "countryCode",
 78 |             "description": "Country code of the location using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code format (e.g. US).",
 79 |             "in": "query",
 80 |             "required": false,
 81 |             "type": "string"
 82 |           },
 83 |           {
 84 |             "$ref": "#/parameters/pageLimit"
 85 |           },
 86 |           {
 87 |             "$ref": "#/parameters/pageOffset"
 88 |           },
 89 |           {
 90 |             "$ref": "#/parameters/sort"
 91 |           },
 92 |           {
 93 |             "$ref": "#/parameters/view"
 94 |           }
 95 |         ],
 96 |         "responses": {
 97 |           "200": {
 98 |             "$ref": "#/responses/airport-city-autocomplete"
 99 |           },
100 |           "400": {
101 |             "$ref": "#/responses/400"
102 |           },
103 |           "default": {
104 |             "$ref": "#/responses/500"
105 |           }
106 |         },
107 |         "description": ""
108 |       }
109 |     },
110 |     "/reference-data/locations/{locationId}": {
111 |       "get": {
112 |         "tags": [
113 |           "location"
114 |         ],
115 |         "operationId": "getAirportCity",
116 |         "summary": "Returns a specific airports or cities based on its id.",
117 |         "parameters": [
118 |           {
119 |             "$ref": "#/parameters/locationId"
120 |           }
121 |         ],
122 |         "responses": {
123 |           "200": {
124 |             "$ref": "#/responses/airport-city"
125 |           },
126 |           "400": {
127 |             "$ref": "#/responses/400_GET-Id"
128 |           },
129 |           "404": {
130 |             "$ref": "#/responses/404"
131 |           },
132 |           "default": {
133 |             "$ref": "#/responses/500"
134 |           }
135 |         },
136 |         "description": ""
137 |       }
138 |     }
139 |   },
140 |   "parameters": {
141 |     "pageLimit": {
142 |       "name": "page[limit]",
143 |       "description": "maximum items in one page",
144 |       "required": false,
145 |       "in": "query",
146 |       "type": "integer",
147 |       "default": 10
148 |     },
149 |     "pageOffset": {
150 |       "name": "page[offset]",
151 |       "description": "start index of the requested page",
152 |       "required": false,
153 |       "in": "query",
154 |       "type": "integer",
155 |       "default": 0
156 |     },
157 |     "sort": {
158 |       "name": "sort",
159 |       "description": "defines on which attribute the sorting will be done:\n* analytics.travelers.score - sort by the number of travelers by airport or city, the airports and cities with the highest traffic are on top of the results\n",
160 |       "required": false,
161 |       "in": "query",
162 |       "type": "string",
163 |       "default": "analytics.travelers.score",
164 |       "enum": [
165 |         "analytics.travelers.score"
166 |       ]
167 |     },
168 |     "view": {
169 |       "name": "view",
170 |       "description": "select the level of information of the reply:\n* LIGHT - Gives only the IATACode, name, detailedName, cityName and countryName\n* FULL - Adds on top of the LIGHT information the timeZoneOffset, geocode, detailed address and travelers.score\ndefault option is FULL\n",
171 |       "required": false,
172 |       "in": "query",
173 |       "default": "FULL",
174 |       "type": "string",
175 |       "enum": [
176 |         "LIGHT",
177 |         "FULL"
178 |       ]
179 |     },
180 |     "locationId": {
181 |       "name": "locationId",
182 |       "description": "identifier of the location",
183 |       "required": true,
184 |       "in": "path",
185 |       "type": "string",
186 |       "x-example": "CMUC"
187 |     }
188 |   },
189 |   "definitions": {
190 |     "Location": {
191 |       "properties": {
192 |         "id": {
193 |           "description": "id of the ressource",
194 |           "type": "string"
195 |         },
196 |         "self": {
197 |           "$ref": "#/definitions/Links"
198 |         },
199 |         "type": {
200 |           "description": "the resource name",
201 |           "type": "string",
202 |           "example": "location"
203 |         },
204 |         "subType": {
205 |           "description": "location sub type",
206 |           "type": "string",
207 |           "enum": [
208 |             "AIRPORT",
209 |             "CITY",
210 |             "POINT_OF_INTEREST",
211 |             "DISTRICT"
212 |           ],
213 |           "example": "AIRPORT"
214 |         },
215 |         "name": {
216 |           "description": "short name of the location",
217 |           "type": "string",
218 |           "example": "Paris CDG"
219 |         },
220 |         "detailedName": {
221 |           "description": "detailed name of the location. For a city location it contains city name and country code. For an airport location it contains city name; country code and airport full name",
222 |           "type": "string",
223 |           "example": "Paris/FR: Charles de Gaulle"
224 |         },
225 |         "timeZoneOffset": {
226 |           "description": "timezone offset of the location at the date of the API call (including daylight saving time)",
227 |           "type": "string",
228 |           "example": "+01:00"
229 |         },
230 |         "iataCode": {
231 |           "description": "IATA code of the location. ([IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) here)",
232 |           "type": "string",
233 |           "example": "CDG"
234 |         },
235 |         "geoCode": {
236 |           "$ref": "#/definitions/GeoCode"
237 |         },
238 |         "address": {
239 |           "$ref": "#/definitions/Address"
240 |         },
241 |         "distance": {
242 |           "$ref": "#/definitions/Distance"
243 |         },
244 |         "analytics": {
245 |           "$ref": "#/definitions/Analytics"
246 |         },
247 |         "relevance": {
248 |           "type": "number",
249 |           "format": "double",
250 |           "description": "score value calculated based on distance and analytics",
251 |           "example": 9.6584
252 |         },
253 |         "category": {
254 |           "description": "category of the location",
255 |           "type": "string",
256 |           "enum": [
257 |             "SIGHTS",
258 |             "BEACH_PARK",
259 |             "HISTORICAL",
260 |             "NIGHTLIFE",
261 |             "RESTAURANT",
262 |             "SHOPPING"
263 |           ],
264 |           "example": "HISTORICAL"
265 |         },
266 |         "tags": {
267 |           "description": "list of tags related to the location",
268 |           "type": "array",
269 |           "items": {
270 |             "type": "string",
271 |             "example": [
272 |               "grocery",
273 |               "japanese",
274 |               "cafe"
275 |             ]
276 |           }
277 |         },
278 |         "rank": {
279 |           "description": "the rank is the position compared to other locations based on how famous is a place. 1 being the highest.",
280 |           "type": "string",
281 |           "example": 1
282 |         }
283 |       }
284 |     },
285 |     "Address": {
286 |       "properties": {
287 |         "cityName": {
288 |           "description": "name of the city of the location; equal to name if the location is a city",
289 |           "type": "string",
290 |           "example": "Paris"
291 |         },
292 |         "cityCode": {
293 |           "description": "IATA code of the city of the location; equal to IATAcode if the location is a city",
294 |           "type": "string",
295 |           "example": "PAR"
296 |         },
297 |         "countryName": {
298 |           "description": "name of the country of the location",
299 |           "type": "string",
300 |           "example": "France"
301 |         },
302 |         "countryCode": {
303 |           "description": "code of the country of the location in ISO standard",
304 |           "type": "string",
305 |           "example": "FR"
306 |         },
307 |         "stateCode": {
308 |           "description": "code of the state of the location if any",
309 |           "type": "string",
310 |           "example": "TO"
311 |         },
312 |         "regionCode": {
313 |           "description": "code of the region of the location in ISO standard",
314 |           "type": "string",
315 |           "example": "EUROP"
316 |         }
317 |       }
318 |     },
319 |     "Distance": {
320 |       "properties": {
321 |         "value": {
322 |           "description": "great-circle distance between two locations. This distance thus do not take into account traffic conditions; international boundaries; mountains; water; or other elements that might make the a nearby location hard to reach.",
323 |           "type": "integer",
324 |           "example": 152
325 |         },
326 |         "unit": {
327 |           "description": "unit of the distance",
328 |           "type": "string",
329 |           "example": "KM",
330 |           "enum": [
331 |             "KM",
332 |             "MI"
333 |           ]
334 |         }
335 |       }
336 |     },
337 |     "GeoCode": {
338 |       "properties": {
339 |         "latitude": {
340 |           "description": "latitude of the location",
341 |           "type": "number",
342 |           "format": "double",
343 |           "example": 43.580418
344 |         },
345 |         "longitude": {
346 |           "description": "longitude of the location",
347 |           "type": "number",
348 |           "format": "double",
349 |           "example": 7.125102
350 |         }
351 |       }
352 |     },
353 |     "Analytics": {
354 |       "properties": {
355 |         "travelers": {
356 |           "$ref": "#/definitions/Travelers"
357 |         }
358 |       }
359 |     },
360 |     "Travelers": {
361 |       "properties": {
362 |         "score": {
363 |           "type": "number",
364 |           "format": "integer",
365 |           "description": "Approximate score for ranking purposes calculated based on number of travelers in the location.",
366 |           "example": 68
367 |         }
368 |       }
369 |     },
370 |     "Error_400": {
371 |       "properties": {
372 |         "errors": {
373 |           "type": "array",
374 |           "items": {
375 |             "$ref": "#/definitions/Issue"
376 |           }
377 |         }
378 |       },
379 |       "required": [
380 |         "errors"
381 |       ],
382 |       "example": {
383 |         "errors": [
384 |           {
385 |             "status": 400,
386 |             "code": 477,
387 |             "title": "INVALID FORMAT",
388 |             "detail": "invalid query parameter format",
389 |             "source": {
390 |               "parameter": "airport",
391 |               "example": "CDG"
392 |             }
393 |           }
394 |         ]
395 |       }
396 |     },
397 |     "Error_404": {
398 |       "properties": {
399 |         "errors": {
400 |           "type": "array",
401 |           "items": {
402 |             "$ref": "#/definitions/Issue"
403 |           }
404 |         }
405 |       },
406 |       "required": [
407 |         "errors"
408 |       ],
409 |       "example": {
410 |         "errors": [
411 |           {
412 |             "status": 404,
413 |             "code": 1797,
414 |             "title": "NOT FOUND",
415 |             "detail": "no response found for this query parameter",
416 |             "source": {
417 |               "parameter": "airport"
418 |             }
419 |           }
420 |         ]
421 |       }
422 |     },
423 |     "Error_500": {
424 |       "properties": {
425 |         "errors": {
426 |           "type": "array",
427 |           "items": {
428 |             "$ref": "#/definitions/Issue"
429 |           }
430 |         }
431 |       },
432 |       "required": [
433 |         "errors"
434 |       ],
435 |       "example": {
436 |         "errors": [
437 |           {
438 |             "status": 500,
439 |             "code": 141,
440 |             "title": "SYSTEM ERROR HAS OCCURRED"
441 |           }
442 |         ]
443 |       }
444 |     },
445 |     "Issue": {
446 |       "properties": {
447 |         "status": {
448 |           "description": "the HTTP status code applicable to this error",
449 |           "type": "integer"
450 |         },
451 |         "code": {
452 |           "description": "an application-specific error code",
453 |           "type": "integer",
454 |           "format": "int64"
455 |         },
456 |         "title": {
457 |           "description": "a short summary of the error",
458 |           "type": "string"
459 |         },
460 |         "detail": {
461 |           "description": "explanation of the error",
462 |           "type": "string"
463 |         },
464 |         "source": {
465 |           "type": "object",
466 |           "title": "Issue_Source",
467 |           "description": "an object containing references to the source of the error",
468 |           "maxProperties": 1,
469 |           "properties": {
470 |             "pointer": {
471 |               "description": "a JSON Pointer [RFC6901] to the associated entity in the request document",
472 |               "type": "string"
473 |             },
474 |             "parameter": {
475 |               "description": "a string indicating which URI query parameter caused the issue",
476 |               "type": "string"
477 |             },
478 |             "example": {
479 |               "description": "a string indicating an example of the right value",
480 |               "type": "string"
481 |             }
482 |           }
483 |         }
484 |       }
485 |     },
486 |     "Collection_Meta": {
487 |       "title": "Collection_Meta",
488 |       "properties": {
489 |         "count": {
490 |           "type": "integer",
491 |           "example": 1
492 |         },
493 |         "links": {
494 |           "title": "CollectionLinks",
495 |           "properties": {
496 |             "self": {
497 |               "type": "string",
498 |               "format": "uri",
499 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
500 |             },
501 |             "next": {
502 |               "type": "string",
503 |               "format": "uri",
504 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
505 |             },
506 |             "previous": {
507 |               "type": "string",
508 |               "format": "uri",
509 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
510 |             },
511 |             "last": {
512 |               "type": "string",
513 |               "format": "uri",
514 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
515 |             },
516 |             "first": {
517 |               "type": "string",
518 |               "format": "uri",
519 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
520 |             },
521 |             "up": {
522 |               "type": "string",
523 |               "format": "uri",
524 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
525 |             }
526 |           },
527 |           "example": {
528 |             "self": "https://test.api.amadeus.com/v1/area/resources?param=value"
529 |           }
530 |         }
531 |       }
532 |     },
533 |     "Links": {
534 |       "required": [
535 |         "href"
536 |       ],
537 |       "properties": {
538 |         "href": {
539 |           "type": "string",
540 |           "format": "uri"
541 |         },
542 |         "methods": {
543 |           "type": "array",
544 |           "items": {
545 |             "type": "string",
546 |             "enum": [
547 |               "GET",
548 |               "PUT",
549 |               "DELETE",
550 |               "POST",
551 |               "PATCH"
552 |             ]
553 |           }
554 |         },
555 |         "count": {
556 |           "type": "integer"
557 |         }
558 |       },
559 |       "example": {
560 |         "href": "string"
561 |       }
562 |     }
563 |   },
564 |   "responses": {
565 |     "400": {
566 |       "description": "code    | title                                 \n------- | ------------------------------------- \n477     | INVALID FORMAT\n572     | INVALID OPTION \n2781    | INVALID LENGTH\n4926    | INVALID DATA RECEIVED               \n32171   | MANDATORY DATA MISSING \t     \n",
567 |       "schema": {
568 |         "$ref": "#/definitions/Error_400"
569 |       }
570 |     },
571 |     "404": {
572 |       "description": "Not Found",
573 |       "schema": {
574 |         "$ref": "#/definitions/Error_404"
575 |       }
576 |     },
577 |     "500": {
578 |       "description": "Unexpected Error",
579 |       "schema": {
580 |         "$ref": "#/definitions/Error_500"
581 |       }
582 |     },
583 |     "airport-city-autocomplete": {
584 |       "description": "Successful Operation",
585 |       "schema": {
586 |         "title": "Success",
587 |         "required": [
588 |           "data"
589 |         ],
590 |         "properties": {
591 |           "meta": {
592 |             "$ref": "#/definitions/Collection_Meta"
593 |           },
594 |           "data": {
595 |             "type": "array",
596 |             "items": {
597 |               "$ref": "#/definitions/Location"
598 |             }
599 |           }
600 |         },
601 |         "example": {
602 |           "meta": {
603 |             "count": 2,
604 |             "links": {
605 |               "self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=CITY,AIRPORT&keyword=MUC&countryCode=DE"
606 |             }
607 |           },
608 |           "data": [
609 |             {
610 |               "type": "location",
611 |               "subType": "CITY",
612 |               "name": "MUNICH INTERNATIONAL",
613 |               "detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
614 |               "id": "CMUC",
615 |               "self": {
616 |                 "href": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC",
617 |                 "methods": [
618 |                   "GET"
619 |                 ]
620 |               },
621 |               "timeZoneOffset": "+02:00",
622 |               "iataCode": "MUC",
623 |               "geoCode": {
624 |                 "latitude": 48.35378,
625 |                 "longitude": 11.78609
626 |               },
627 |               "address": {
628 |                 "cityName": "MUNICH",
629 |                 "cityCode": "MUC",
630 |                 "countryName": "GERMANY",
631 |                 "countryCode": "DE",
632 |                 "regionCode": "EUROP"
633 |               },
634 |               "analytics": {
635 |                 "travelers": {
636 |                   "score": 27
637 |                 }
638 |               }
639 |             },
640 |             {
641 |               "type": "location",
642 |               "subType": "AIRPORT",
643 |               "name": "MUNICH INTERNATIONAL",
644 |               "detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
645 |               "id": "AMUC",
646 |               "self": {
647 |                 "href": "https://test.api.amadeus.com/v1/reference-data/locations/AMUC",
648 |                 "methods": [
649 |                   "GET"
650 |                 ]
651 |               },
652 |               "timeZoneOffset": "+02:00",
653 |               "iataCode": "MUC",
654 |               "geoCode": {
655 |                 "latitude": 48.35378,
656 |                 "longitude": 11.78609
657 |               },
658 |               "address": {
659 |                 "cityName": "MUNICH",
660 |                 "cityCode": "MUC",
661 |                 "countryName": "GERMANY",
662 |                 "countryCode": "DE",
663 |                 "regionCode": "EUROP"
664 |               },
665 |               "analytics": {
666 |                 "travelers": {
667 |                   "score": 27
668 |                 }
669 |               }
670 |             }
671 |           ]
672 |         }
673 |       }
674 |     },
675 |     "airport-city": {
676 |       "description": "Successful Operation",
677 |       "schema": {
678 |         "title": "Success",
679 |         "required": [
680 |           "data"
681 |         ],
682 |         "properties": {
683 |           "meta": {
684 |             "$ref": "#/definitions/Collection_Meta"
685 |           },
686 |           "data": {
687 |             "$ref": "#/definitions/Location"
688 |           }
689 |         },
690 |         "example": {
691 |           "meta": {
692 |             "links": {
693 |               "self": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC"
694 |             }
695 |           },
696 |           "data": {
697 |             "type": "location",
698 |             "subType": "CITY",
699 |             "name": "MUNICH INTERNATIONAL",
700 |             "detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
701 |             "id": "CMUC",
702 |             "self": {
703 |               "href": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC",
704 |               "methods": [
705 |                 "GET"
706 |               ]
707 |             },
708 |             "timeZoneOffset": "+02:00",
709 |             "iataCode": "MUC",
710 |             "geoCode": {
711 |               "latitude": 48.35378,
712 |               "longitude": 11.78609
713 |             },
714 |             "address": {
715 |               "cityName": "MUNICH",
716 |               "cityCode": "MUC",
717 |               "countryName": "GERMANY",
718 |               "countryCode": "DE",
719 |               "regionCode": "EUROP"
720 |             },
721 |             "analytics": {
722 |               "travelers": {
723 |                 "score": 27
724 |               }
725 |             }
726 |           }
727 |         }
728 |       }
729 |     },
730 |     "400_GET-Id": {
731 |       "description": "code    | title                                 \n------- | ------------------------------------- \n572     | INVALID OPTION    \n",
732 |       "schema": {
733 |         "$ref": "#/definitions/Error_400"
734 |       }
735 |     }
736 |   },
737 |   "x-generatedAt": "2020-07-24T09:50:53.074Z"
738 | }
```

--------------------------------------------------------------------------------
/api-spectifications/AirportNearestRelevant_v1_Version_1.0_swagger_specification.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "swagger": "2.0",
  3 |   "info": {
  4 |     "version": "1.1.2",
  5 |     "title": "Airport Nearest Relevant",
  6 |     "x-tags": [
  7 |       "#ama-for-dev"
  8 |     ],
  9 |     "x-status": "validated",
 10 |     "x-release-note": {
 11 |       "1.1.1": [
 12 |         "Correct example"
 13 |       ],
 14 |       "1.1.0": [
 15 |         "Add radius parameter"
 16 |       ],
 17 |       "1.0.2": [
 18 |         "AFD-1091 - change from \"traveller\" to \"traveler\""
 19 |       ],
 20 |       "1.0.1": [
 21 |         "Improvement of relevance calculation",
 22 |         "Remove flights and travelers figures",
 23 |         "Flights and Travelers score become integer (PTR 14827552)"
 24 |       ],
 25 |       "1.0": [
 26 |         "Initial Version"
 27 |       ]
 28 |     },
 29 |     "description": "\nBefore using this API, we recommend you read our **[Authorization Guide](https://developers.amadeus.com/self-service/apis-docs/guides/authorization-262)** for more information on how to generate an access token.\n\nPlease also be aware that our test environment is based on a subset of the production, this API in test only returns a few selected cities. You can find the list in our **[data collection](https://github.com/amadeus4dev/data-collection)**."
 30 |   },
 31 |   "host": "test.api.amadeus.com",
 32 |   "basePath": "/v1",
 33 |   "schemes": [
 34 |     "https"
 35 |   ],
 36 |   "consumes": [
 37 |     "application/vnd.amadeus+json"
 38 |   ],
 39 |   "produces": [
 40 |     "application/vnd.amadeus+json"
 41 |   ],
 42 |   "paths": {
 43 |     "/reference-data/locations/airports": {
 44 |       "get": {
 45 |         "tags": [
 46 |           "location"
 47 |         ],
 48 |         "operationId": "getNearestRelevantAirports",
 49 |         "summary": "Returns a list of relevant airports near to a given point.",
 50 |         "parameters": [
 51 |           {
 52 |             "name": "latitude",
 53 |             "description": "latitude location to be at the center of the search circle",
 54 |             "in": "query",
 55 |             "required": true,
 56 |             "type": "number",
 57 |             "format": "double",
 58 |             "x-example": 51.57285
 59 |           },
 60 |           {
 61 |             "name": "longitude",
 62 |             "description": "longitude location to be at the center of the search circle",
 63 |             "in": "query",
 64 |             "required": true,
 65 |             "type": "number",
 66 |             "format": "double",
 67 |             "x-example": -0.44161
 68 |           },
 69 |           {
 70 |             "name": "radius",
 71 |             "description": "radius of the search in Kilometer. Can be from 0 to 500, default value is 500 Km.",
 72 |             "in": "query",
 73 |             "required": false,
 74 |             "type": "integer",
 75 |             "minimum": 0,
 76 |             "maximum": 500,
 77 |             "default": 500
 78 |           },
 79 |           {
 80 |             "$ref": "#/parameters/pageLimit"
 81 |           },
 82 |           {
 83 |             "$ref": "#/parameters/pageOffset"
 84 |           },
 85 |           {
 86 |             "$ref": "#/parameters/sort"
 87 |           }
 88 |         ],
 89 |         "responses": {
 90 |           "200": {
 91 |             "$ref": "#/responses/nearest-relevant-airports"
 92 |           },
 93 |           "400": {
 94 |             "$ref": "#/responses/400"
 95 |           },
 96 |           "default": {
 97 |             "$ref": "#/responses/500"
 98 |           }
 99 |         },
100 |         "description": ""
101 |       }
102 |     }
103 |   },
104 |   "parameters": {
105 |     "pageLimit": {
106 |       "name": "page[limit]",
107 |       "description": "maximum items in one page",
108 |       "required": false,
109 |       "in": "query",
110 |       "type": "integer",
111 |       "default": 10
112 |     },
113 |     "pageOffset": {
114 |       "name": "page[offset]",
115 |       "description": "start index of the requested page",
116 |       "required": false,
117 |       "in": "query",
118 |       "type": "integer",
119 |       "default": 0
120 |     },
121 |     "sort": {
122 |       "description": "defines on which attribute the sorting will be done from the best option to the worst one:\n* **relevance** - Score value calculated based on distance and traffic analytics\n* **distance** - Distance from the location to the geo-code given in API request parameters\n* **analytics.flights.score** - Approximate score for ranking purposes calculated based on estimated number of flights from/to airport in one reference year (last year)\n* **analytics.travelers.score** - Approximate score for ranking purposes calculated based on estimated number of travelers in the airport for one reference year (last year)\n",
123 |       "name": "sort",
124 |       "required": false,
125 |       "in": "query",
126 |       "type": "string",
127 |       "default": "relevance",
128 |       "enum": [
129 |         "relevance",
130 |         "distance",
131 |         "analytics.flights.score",
132 |         "analytics.travelers.score"
133 |       ]
134 |     }
135 |   },
136 |   "definitions": {
137 |     "Location": {
138 |       "properties": {
139 |         "type": {
140 |           "description": "the resource name",
141 |           "type": "string",
142 |           "example": "location"
143 |         },
144 |         "subType": {
145 |           "description": "location sub type",
146 |           "type": "string",
147 |           "enum": [
148 |             "AIRPORT",
149 |             "CITY",
150 |             "POINT_OF_INTEREST",
151 |             "DISTRICT"
152 |           ],
153 |           "example": "AIRPORT"
154 |         },
155 |         "name": {
156 |           "description": "short name of the location",
157 |           "type": "string",
158 |           "example": "Paris CDG"
159 |         },
160 |         "detailedName": {
161 |           "description": "detailed name of the location. For a city location it contains city name and country code. For an airport location it contains city name; country code and airport full name",
162 |           "type": "string",
163 |           "example": "Paris/FR: Charles de Gaulle"
164 |         },
165 |         "timeZoneOffset": {
166 |           "description": "timezone offset of the location at the date of the API call (including daylight saving time)",
167 |           "type": "string",
168 |           "example": "+01:00"
169 |         },
170 |         "iataCode": {
171 |           "description": "IATA code of the location. ([IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) here)",
172 |           "type": "string",
173 |           "example": "CDG"
174 |         },
175 |         "geoCode": {
176 |           "$ref": "#/definitions/GeoCode"
177 |         },
178 |         "address": {
179 |           "$ref": "#/definitions/Address"
180 |         },
181 |         "distance": {
182 |           "$ref": "#/definitions/Distance"
183 |         },
184 |         "analytics": {
185 |           "$ref": "#/definitions/Analytics"
186 |         },
187 |         "relevance": {
188 |           "type": "number",
189 |           "format": "double",
190 |           "description": "score value calculated based on distance and analytics",
191 |           "example": 9.6584
192 |         }
193 |       }
194 |     },
195 |     "Address": {
196 |       "properties": {
197 |         "cityName": {
198 |           "description": "name of the city of the location; equal to name if the location is a city",
199 |           "type": "string",
200 |           "example": "Paris"
201 |         },
202 |         "cityCode": {
203 |           "description": "IATA code of the city of the location; equal to IATAcode if the location is a city",
204 |           "type": "string",
205 |           "example": "PAR"
206 |         },
207 |         "countryName": {
208 |           "description": "name of the country of the location",
209 |           "type": "string",
210 |           "example": "France"
211 |         },
212 |         "countryCode": {
213 |           "description": "code of the country of the location in ISO standard",
214 |           "type": "string",
215 |           "example": "FR"
216 |         },
217 |         "stateCode": {
218 |           "description": "code of the state of the location if any",
219 |           "type": "string",
220 |           "example": "TO"
221 |         },
222 |         "regionCode": {
223 |           "description": "code of the region of the location in ISO standard",
224 |           "type": "string",
225 |           "example": "EUROP"
226 |         }
227 |       }
228 |     },
229 |     "Distance": {
230 |       "properties": {
231 |         "value": {
232 |           "description": "great-circle distance between two locations. This distance thus do not take into account traffic conditions; international boundaries; mountains; water; or other elements that might make the a nearby location hard to reach.",
233 |           "type": "integer",
234 |           "example": 152
235 |         },
236 |         "unit": {
237 |           "description": "unit of the distance",
238 |           "type": "string",
239 |           "example": "KM",
240 |           "enum": [
241 |             "KM",
242 |             "MI"
243 |           ]
244 |         }
245 |       }
246 |     },
247 |     "GeoCode": {
248 |       "properties": {
249 |         "latitude": {
250 |           "description": "latitude of the location",
251 |           "type": "number",
252 |           "format": "double",
253 |           "example": 43.580418
254 |         },
255 |         "longitude": {
256 |           "description": "longitude of the location",
257 |           "type": "number",
258 |           "format": "double",
259 |           "example": 7.125102
260 |         }
261 |       }
262 |     },
263 |     "Analytics": {
264 |       "properties": {
265 |         "flights": {
266 |           "$ref": "#/definitions/Flights"
267 |         },
268 |         "travelers": {
269 |           "$ref": "#/definitions/Travelers"
270 |         }
271 |       }
272 |     },
273 |     "Flights": {
274 |       "properties": {
275 |         "score": {
276 |           "type": "number",
277 |           "format": "integer",
278 |           "description": "Approximate score for ranking purposes calculated based on number of flights from / to the airport or city",
279 |           "example": 56
280 |         }
281 |       }
282 |     },
283 |     "Travelers": {
284 |       "properties": {
285 |         "score": {
286 |           "type": "number",
287 |           "format": "integer",
288 |           "description": "Approximate score for ranking purposes calculated based on number of travelers in the location.",
289 |           "example": 68
290 |         }
291 |       }
292 |     },
293 |     "Error_400": {
294 |       "properties": {
295 |         "errors": {
296 |           "type": "array",
297 |           "items": {
298 |             "$ref": "#/definitions/Issue"
299 |           }
300 |         }
301 |       },
302 |       "required": [
303 |         "errors"
304 |       ],
305 |       "example": {
306 |         "errors": [
307 |           {
308 |             "status": 400,
309 |             "code": 477,
310 |             "title": "INVALID FORMAT",
311 |             "detail": "invalid query parameter format",
312 |             "source": {
313 |               "parameter": "airport",
314 |               "example": "CDG"
315 |             }
316 |           }
317 |         ]
318 |       }
319 |     },
320 |     "Error_500": {
321 |       "properties": {
322 |         "errors": {
323 |           "type": "array",
324 |           "items": {
325 |             "$ref": "#/definitions/Issue"
326 |           }
327 |         }
328 |       },
329 |       "required": [
330 |         "errors"
331 |       ],
332 |       "example": {
333 |         "errors": [
334 |           {
335 |             "status": 500,
336 |             "code": 141,
337 |             "title": "SYSTEM ERROR HAS OCCURRED"
338 |           }
339 |         ]
340 |       }
341 |     },
342 |     "Issue": {
343 |       "properties": {
344 |         "status": {
345 |           "description": "the HTTP status code applicable to this error",
346 |           "type": "integer"
347 |         },
348 |         "code": {
349 |           "description": "an application-specific error code",
350 |           "type": "integer",
351 |           "format": "int64"
352 |         },
353 |         "title": {
354 |           "description": "a short summary of the error",
355 |           "type": "string"
356 |         },
357 |         "detail": {
358 |           "description": "explanation of the error",
359 |           "type": "string"
360 |         },
361 |         "source": {
362 |           "type": "object",
363 |           "title": "Issue_Source",
364 |           "description": "an object containing references to the source of the error",
365 |           "maxProperties": 1,
366 |           "properties": {
367 |             "pointer": {
368 |               "description": "a JSON Pointer [RFC6901] to the associated entity in the request document",
369 |               "type": "string"
370 |             },
371 |             "parameter": {
372 |               "description": "a string indicating which URI query parameter caused the issue",
373 |               "type": "string"
374 |             },
375 |             "example": {
376 |               "description": "a string indicating an example of the right value",
377 |               "type": "string"
378 |             }
379 |           }
380 |         }
381 |       }
382 |     },
383 |     "Collection_Meta": {
384 |       "title": "Collection_Meta",
385 |       "properties": {
386 |         "count": {
387 |           "type": "integer",
388 |           "example": 1
389 |         },
390 |         "links": {
391 |           "title": "CollectionLinks",
392 |           "properties": {
393 |             "self": {
394 |               "type": "string",
395 |               "format": "uri",
396 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
397 |             },
398 |             "next": {
399 |               "type": "string",
400 |               "format": "uri",
401 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
402 |             },
403 |             "previous": {
404 |               "type": "string",
405 |               "format": "uri",
406 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
407 |             },
408 |             "last": {
409 |               "type": "string",
410 |               "format": "uri",
411 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
412 |             },
413 |             "first": {
414 |               "type": "string",
415 |               "format": "uri",
416 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
417 |             },
418 |             "up": {
419 |               "type": "string",
420 |               "format": "uri",
421 |               "example": "https://test.api.amadeus.com/v1/area/resources?..."
422 |             }
423 |           },
424 |           "example": {
425 |             "self": "https://test.api.amadeus.com/v1/area/resources?param=value"
426 |           }
427 |         }
428 |       }
429 |     }
430 |   },
431 |   "responses": {
432 |     "400": {
433 |       "description": "code    | title                                 \n------- | ------------------------------------- \n477     | INVALID FORMAT\n572     | INVALID OPTION\n4926    | INVALID DATA RECEIVED               \n32171   | MANDATORY DATA MISSING \t     \n",
434 |       "schema": {
435 |         "$ref": "#/definitions/Error_400"
436 |       }
437 |     },
438 |     "500": {
439 |       "description": "Unexpected Error",
440 |       "schema": {
441 |         "$ref": "#/definitions/Error_500"
442 |       }
443 |     },
444 |     "nearest-relevant-airports": {
445 |       "description": "Successful Operation",
446 |       "schema": {
447 |         "title": "Success",
448 |         "required": [
449 |           "data"
450 |         ],
451 |         "properties": {
452 |           "meta": {
453 |             "$ref": "#/definitions/Collection_Meta"
454 |           },
455 |           "data": {
456 |             "type": "array",
457 |             "items": {
458 |               "$ref": "#/definitions/Location"
459 |             }
460 |           }
461 |         },
462 |         "example": {
463 |           "meta": {
464 |             "count": 32,
465 |             "links": {
466 |               "self": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161",
467 |               "next": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161&page%5Boffset%5D=10",
468 |               "last": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161&page%5Boffset%5D=22"
469 |             }
470 |           },
471 |           "data": [
472 |             {
473 |               "type": "location",
474 |               "subType": "AIRPORT",
475 |               "name": "HEATHROW",
476 |               "detailedName": "LONDON/GB:HEATHROW",
477 |               "timeZoneOffset": "+01:00",
478 |               "iataCode": "LHR",
479 |               "geoCode": {
480 |                 "latitude": 51.47294,
481 |                 "longitude": -0.45061
482 |               },
483 |               "address": {
484 |                 "cityName": "LONDON",
485 |                 "cityCode": "LON",
486 |                 "countryName": "UNITED KINGDOM",
487 |                 "countryCode": "GB",
488 |                 "regionCode": "EUROP"
489 |               },
490 |               "distance": {
491 |                 "value": 11,
492 |                 "unit": "KM"
493 |               },
494 |               "analytics": {
495 |                 "flights": {
496 |                   "score": 39
497 |                 },
498 |                 "travelers": {
499 |                   "score": 45
500 |                 }
501 |               },
502 |               "relevance": 350.54587
503 |             },
504 |             {
505 |               "type": "location",
506 |               "subType": "AIRPORT",
507 |               "name": "GATWICK",
508 |               "detailedName": "LONDON/GB:GATWICK",
509 |               "timeZoneOffset": "+01:00",
510 |               "iataCode": "LGW",
511 |               "geoCode": {
512 |                 "latitude": 51.15609,
513 |                 "longitude": -0.17818
514 |               },
515 |               "address": {
516 |                 "cityName": "LONDON",
517 |                 "cityCode": "LON",
518 |                 "countryName": "UNITED KINGDOM",
519 |                 "countryCode": "GB",
520 |                 "regionCode": "EUROP"
521 |               },
522 |               "distance": {
523 |                 "value": 49,
524 |                 "unit": "KM"
525 |               },
526 |               "analytics": {
527 |                 "flights": {
528 |                   "score": 27
529 |                 },
530 |                 "travelers": {
531 |                   "score": 27
532 |                 }
533 |               },
534 |               "relevance": 53.62667
535 |             },
536 |             {
537 |               "type": "location",
538 |               "subType": "AIRPORT",
539 |               "name": "LUTON",
540 |               "detailedName": "LONDON/GB:LUTON",
541 |               "timeZoneOffset": "+01:00",
542 |               "iataCode": "LTN",
543 |               "geoCode": {
544 |                 "latitude": 51.87472,
545 |                 "longitude": -0.36833
546 |               },
547 |               "address": {
548 |                 "cityName": "LONDON",
549 |                 "cityCode": "LON",
550 |                 "countryName": "UNITED KINGDOM",
551 |                 "countryCode": "GB",
552 |                 "regionCode": "EUROP"
553 |               },
554 |               "distance": {
555 |                 "value": 33,
556 |                 "unit": "KM"
557 |               },
558 |               "analytics": {
559 |                 "flights": {
560 |                   "score": 11
561 |                 },
562 |                 "travelers": {
563 |                   "score": 10
564 |                 }
565 |               },
566 |               "relevance": 33.10184
567 |             },
568 |             {
569 |               "type": "location",
570 |               "subType": "AIRPORT",
571 |               "name": "STANSTED",
572 |               "detailedName": "LONDON/GB:STANSTED",
573 |               "timeZoneOffset": "+01:00",
574 |               "iataCode": "STN",
575 |               "geoCode": {
576 |                 "latitude": 51.885,
577 |                 "longitude": 0.235
578 |               },
579 |               "address": {
580 |                 "cityName": "LONDON",
581 |                 "cityCode": "LON",
582 |                 "countryName": "UNITED KINGDOM",
583 |                 "countryCode": "GB",
584 |                 "regionCode": "EUROP"
585 |               },
586 |               "distance": {
587 |                 "value": 58,
588 |                 "unit": "KM"
589 |               },
590 |               "analytics": {
591 |                 "flights": {
592 |                   "score": 16
593 |                 },
594 |                 "travelers": {
595 |                   "score": 15
596 |                 }
597 |               },
598 |               "relevance": 27.50241
599 |             },
600 |             {
601 |               "type": "location",
602 |               "subType": "AIRPORT",
603 |               "name": "CITY AIRPORT",
604 |               "detailedName": "LONDON/GB:CITY AIRPORT",
605 |               "timeZoneOffset": "+01:00",
606 |               "iataCode": "LCY",
607 |               "geoCode": {
608 |                 "latitude": 51.50528,
609 |                 "longitude": 0.05528
610 |               },
611 |               "address": {
612 |                 "cityName": "LONDON",
613 |                 "cityCode": "LON",
614 |                 "countryName": "UNITED KINGDOM",
615 |                 "countryCode": "GB",
616 |                 "regionCode": "EUROP"
617 |               },
618 |               "distance": {
619 |                 "value": 35,
620 |                 "unit": "KM"
621 |               },
622 |               "analytics": {
623 |                 "flights": {
624 |                   "score": 8
625 |                 },
626 |                 "travelers": {
627 |                   "score": 4
628 |                 }
629 |               },
630 |               "relevance": 21.78754
631 |             },
632 |             {
633 |               "type": "location",
634 |               "subType": "AIRPORT",
635 |               "name": "BIRMINGHAM",
636 |               "detailedName": "BIRMINGHAM/GB:BIRMINGHAM",
637 |               "timeZoneOffset": "+01:00",
638 |               "iataCode": "BHX",
639 |               "geoCode": {
640 |                 "latitude": 52.45386,
641 |                 "longitude": -1.74803
642 |               },
643 |               "address": {
644 |                 "cityName": "BIRMINGHAM",
645 |                 "cityCode": "BHX",
646 |                 "countryName": "UNITED KINGDOM",
647 |                 "countryCode": "GB",
648 |                 "regionCode": "EUROP"
649 |               },
650 |               "distance": {
651 |                 "value": 132,
652 |                 "unit": "KM"
653 |               },
654 |               "analytics": {
655 |                 "flights": {
656 |                   "score": 10
657 |                 },
658 |                 "travelers": {
659 |                   "score": 8
660 |                 }
661 |               },
662 |               "relevance": 7.73356
663 |             },
664 |             {
665 |               "type": "location",
666 |               "subType": "AIRPORT",
667 |               "name": "MANCHESTER AIRPORT",
668 |               "detailedName": "MANCHESTER/GB:MANCHESTER AIRPO",
669 |               "timeZoneOffset": "+01:00",
670 |               "iataCode": "MAN",
671 |               "geoCode": {
672 |                 "latitude": 53.35374,
673 |                 "longitude": -2.27495
674 |               },
675 |               "address": {
676 |                 "cityName": "MANCHESTER",
677 |                 "cityCode": "MAN",
678 |                 "countryName": "UNITED KINGDOM",
679 |                 "countryCode": "GB",
680 |                 "regionCode": "EUROP"
681 |               },
682 |               "distance": {
683 |                 "value": 233,
684 |                 "unit": "KM"
685 |               },
686 |               "analytics": {
687 |                 "flights": {
688 |                   "score": 18
689 |                 },
690 |                 "travelers": {
691 |                   "score": 17
692 |                 }
693 |               },
694 |               "relevance": 7.71084
695 |             },
696 |             {
697 |               "type": "location",
698 |               "subType": "AIRPORT",
699 |               "name": "SOUTHAMPTON",
700 |               "detailedName": "SOUTHAMPTON/GB",
701 |               "timeZoneOffset": "+01:00",
702 |               "iataCode": "SOU",
703 |               "geoCode": {
704 |                 "latitude": 50.95026,
705 |                 "longitude": -1.3568
706 |               },
707 |               "address": {
708 |                 "cityName": "SOUTHAMPTON",
709 |                 "cityCode": "SOU",
710 |                 "countryName": "UNITED KINGDOM",
711 |                 "countryCode": "GB",
712 |                 "regionCode": "EUROP"
713 |               },
714 |               "distance": {
715 |                 "value": 94,
716 |                 "unit": "KM"
717 |               },
718 |               "analytics": {
719 |                 "flights": {
720 |                   "score": 4
721 |                 },
722 |                 "travelers": {
723 |                   "score": 2
724 |                 }
725 |               },
726 |               "relevance": 4.4788
727 |             },
728 |             {
729 |               "type": "location",
730 |               "subType": "AIRPORT",
731 |               "name": "BRISTOL",
732 |               "detailedName": "BRISTOL/GB:BRISTOL",
733 |               "timeZoneOffset": "+01:00",
734 |               "iataCode": "BRS",
735 |               "geoCode": {
736 |                 "latitude": 51.38267,
737 |                 "longitude": -2.71909
738 |               },
739 |               "address": {
740 |                 "cityName": "BRISTOL",
741 |                 "cityCode": "BRS",
742 |                 "countryName": "UNITED KINGDOM",
743 |                 "countryCode": "GB",
744 |                 "regionCode": "EUROP"
745 |               },
746 |               "distance": {
747 |                 "value": 159,
748 |                 "unit": "KM"
749 |               },
750 |               "analytics": {
751 |                 "flights": {
752 |                   "score": 7
753 |                 },
754 |                 "travelers": {
755 |                   "score": 5
756 |                 }
757 |               },
758 |               "relevance": 4.08617
759 |             },
760 |             {
761 |               "type": "location",
762 |               "subType": "AIRPORT",
763 |               "name": "EAST MIDLANDS",
764 |               "detailedName": "NOTTINGHAM/GB:EAST MIDLANDS",
765 |               "timeZoneOffset": "+01:00",
766 |               "iataCode": "EMA",
767 |               "geoCode": {
768 |                 "latitude": 52.83111,
769 |                 "longitude": -1.32806
770 |               },
771 |               "address": {
772 |                 "cityName": "NOTTINGHAM",
773 |                 "cityCode": "NQT",
774 |                 "countryName": "UNITED KINGDOM",
775 |                 "countryCode": "GB",
776 |                 "regionCode": "EUROP"
777 |               },
778 |               "distance": {
779 |                 "value": 152,
780 |                 "unit": "KM"
781 |               },
782 |               "analytics": {
783 |                 "flights": {
784 |                   "score": 4
785 |                 },
786 |                 "travelers": {
787 |                   "score": 3
788 |                 }
789 |               },
790 |               "relevance": 2.66099
791 |             }
792 |           ]
793 |         }
794 |       }
795 |     }
796 |   },
797 |   "x-generatedAt": "2020-07-22T14:53:48.686Z"
798 | }
```
Page 1/2FirstPrevNextLast