This is page 1 of 2. Use http://codebase.md/privilegemendes/amadeus-mcp-server-standalone?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:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
.pnpm-debug.log
# Environment variables
.env
.env
.env.development.local
.env.test.local
.env.production.local
# Build output
dist/
build/
out/
*.tsbuildinfo
# Coverage directory used by tools like Jest
coverage/
# Logs
logs
*.log
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# JetBrains IDEs
.idea/
*.iml
*.iws
*.ipr
.idea_modules/
# macOS
.DS_Store
.AppleDouble
.LSOverride
._*
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Testing
*.spec.results
# Debug
.node-debug/
# Temp files
tmp/
temp/
.tmp/
.temp/
# Cache
.cache/
.npm
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Amadeus MCP Server
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.
## Features
- **Flight Search**: Find flights between airports with various parameters
- **Airport Information**: Search for airports by keyword, city, or country
- **Price Analysis**: Get price metrics for routes to determine if current prices are high or low
- **Cheapest Dates**: Find the most economical dates to travel
- **Flight Details**: Get detailed information about specific flight offers
## Prompts
The server provides several pre-configured prompts for common travel planning scenarios:
1. **Analyze Flight Prices** (`analyze-flight-prices`): Analyze flight prices for a route with insights on pricing trends
2. **Find Best Deals** (`find-best-deals`): Find the best flight deals for a specific route and date
3. **Plan Multi-City Trip** (`plan-multi-city-trip`): Plan a complete multi-city itinerary with optimal routing
4. **Find Cheapest Travel Dates** (`find-cheapest-travel-dates`): Identify the most economical dates to travel
## Setup
### Prerequisites
- Node.js 16.x or higher
- Amadeus API credentials (Client ID and Secret)
### Installation
1. Clone the repository:
```
git clone https://github.com/yourusername/amadeus-mcp-server.git
cd amadeus-mcp-server
```
2. Install dependencies:
```
npm install
```
3. Create a `.env` file in the root directory with your Amadeus API credentials:
```
AMADEUS_CLIENT_ID=your_client_id
AMADEUS_CLIENT_SECRET=your_client_secret
```
### Running the Server
Build and start the server:
```
npm run build
npm start
```
For development:
```
npm run dev
```
### Testing and Development
This project uses Jest for testing and Biome for linting and formatting.
Run unit tests:
```
npx jest
```
Run tests with watch mode:
```
npx jest --watch
```
Run tests with coverage:
```
npx jest --coverage
```
Run integration tests (requires Amadeus API credentials):
```
npm run test:integration
```
Run linting:
```
npm run lint
```
Format code:
```
npm run format
```
## Integration Testing
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.
### Requirements for Integration Tests
- **Amadeus API Credentials**: Tests require valid Amadeus API credentials in the `.env` file:
```
AMADEUS_CLIENT_ID=your_client_id
AMADEUS_CLIENT_SECRET=your_client_secret
```
- **Test Environment**: Tests are configured to use the Amadeus Test Environment, not the production API.
### Running Integration Tests
```
npm run test:integration
```
The integration tests are located in `__tests__/integration` and validate the following API features:
- **Airport Search**: Searching for airports by code or keyword
- **Flight Search**: Finding flights for one-way and round-trip journeys
- **Price Analysis**: Getting price metrics for specific routes
### Best Practices for Integration Testing
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.
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.
3. **Test in Isolation**: When developing a new feature, you can run specific test files:
```
npx jest __tests__/integration/flight-search.test.js
```
4. **Longer Timeouts**: Integration tests use longer timeouts (60 seconds) to accommodate network latency and retries.
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.
## Integration
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.
## Tools
The server provides the following tools:
### `search-flights`
Search for flight offers between two locations.
### `search-airports`
Search for airports by keyword, city name, or IATA code.
### `flight-price-analysis`
Get price metrics for a flight route to determine if current prices are high or low.
### `get-flight-details`
Get detailed information about a specific flight offer.
### `find-cheapest-dates`
Find the cheapest dates to fly for a given route.
## Resources
The server provides schema resources for:
- Flight offers (`schema://flight-offers`)
- Airports (`schema://airports`)
## License
MIT
```
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
```javascript
/** @type {import('jest').Config} */
module.exports = {
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.js'],
};
```
--------------------------------------------------------------------------------
/railway.toml:
--------------------------------------------------------------------------------
```toml
[build]
builder = "nixpacks"
buildCommand = "npm install && npm run build"
[deploy]
startCommand = "node dist/cli.js"
healthcheckPath = "/health"
healthcheckTimeout = 100
restartPolicyType = "on_failure"
[env]
NODE_ENV = "production"
```
--------------------------------------------------------------------------------
/__tests__/basic.test.js:
--------------------------------------------------------------------------------
```javascript
// No imports needed for basic tests
describe('Basic syntax tests', () => {
test('JavaScript syntax test', () => {
// Test basic JavaScript features
const array = [1, 2, 3];
expect(array.length).toBe(3);
const obj = { name: 'Test', value: 42 };
expect(obj.name).toBe('Test');
expect(obj.value).toBe(42);
});
test('Async/await support', async () => {
const result = await Promise.resolve('async works');
expect(result).toBe('async works');
});
test('Arrow functions', () => {
const add = (a, b) => a + b;
expect(add(2, 3)).toBe(5);
});
});
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": false,
"skipLibCheck": true,
"outDir": "./dist",
"declaration": true,
"sourceMap": true,
"allowJs": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://biomejs.dev/schemas/1.7.2/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "warn",
"noNonNullAssertion": "warn"
},
"correctness": {
"useExhaustiveDependencies": "warn"
},
"suspicious": {
"noExplicitAny": "warn"
},
"a11y": {
"useHtmlLang": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"indentStyle": "space"
}
},
"files": {
"ignore": [
".next/*",
"coverage/*",
"node_modules/*",
"package.json",
"package-lock.json",
"tsconfig.json",
"renovate.json"
]
}
}
```
--------------------------------------------------------------------------------
/scripts/kill-ports.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
const { exec } = require('node:child_process');
const { promisify } = require('node:util');
const execAsync = promisify(exec);
// Common ports that might be used by the MCP server
const ports = [3000, 3001, 3002, 8080, 8081, 8082, 5173];
async function killPort(port) {
try {
// For macOS/Linux
const { stdout } = await execAsync(`lsof -i :${port} -t`);
if (stdout.trim()) {
const pid = stdout.trim();
await execAsync(`kill -9 ${pid}`);
console.log(`Killed process on port ${port} (PID: ${pid})`);
} else {
console.log(`No process found on port ${port}`);
}
} catch (error) {
if (error.message.includes('No such process')) {
console.log(`No process found on port ${port}`);
} else {
console.error(`Error checking port ${port}:`, error.message);
}
}
}
async function killAllPorts() {
console.log('Checking and killing processes on common ports...');
for (const port of ports) {
await killPort(port);
}
console.log('\nDone! All common ports have been checked.');
}
// Run the script
killAllPorts().catch(console.error);
```
--------------------------------------------------------------------------------
/test-sse.js:
--------------------------------------------------------------------------------
```javascript
import EventSource from 'eventsource';
import fetch from 'node-fetch';
const BASE_URL = 'https://amadeus-mcp-server-production-5e4a.up.railway.app';
// Connect to SSE endpoint
const sse = new EventSource(`${BASE_URL}/sse`);
let connectionId = null;
// Listen for the initial connection message
sse.onmessage = async (event) => {
const data = JSON.parse(event.data);
if (data.connectionId) {
connectionId = data.connectionId;
console.log('Connected with ID:', connectionId);
// Once we have the connection ID, send a test message
try {
const response = await fetch(`${BASE_URL}/messages?connectionId=${connectionId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'test',
payload: { message: 'Hello from test client!' }
})
});
const result = await response.json();
console.log('Message sent response:', result);
} catch (error) {
console.error('Error sending message:', error);
}
} else {
console.log('Received message:', data);
}
};
sse.onerror = (error) => {
console.error('SSE Error:', error);
};
// Keep the script running
process.on('SIGINT', () => {
sse.close();
process.exit();
});
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@privilegemendes/amadeus-mcp-server",
"version": "1.0.4",
"description": "MCP server for Amadeus flight search and booking",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"amadeus-mcp-server": "./dist/cli.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && tsc && chmod +x dist/cli.js",
"start": "node dist/cli.js",
"dev": "ts-node src/index.ts",
"lint": "biome check src/",
"format": "biome format --write src/",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:integration": "jest __tests__/integration",
"test:inspector": "node __tests__/mcp-inspector.test.js",
"mcp:inspect": "npx @modelcontextprotocol/inspector node dist/cli.js",
"mcp:test": "node scripts/test-tools.js",
"kill-ports": "node scripts/kill-ports.js",
"prepublishOnly": "npm run build",
"publish": "npm publish"
},
"keywords": [
"mcp",
"amadeus",
"flight-search",
"model-context-protocol",
"ai"
],
"author": "Privilege Mendes",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"amadeus": "^7.1.0",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"node-cache": "^5.1.2",
"zod": "^3.20.6"
},
"devDependencies": {
"@biomejs/biome": "1.7.2",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.11",
"@types/node": "^18.14.0",
"eventsource": "^3.0.5",
"jest": "^29.7.0",
"node-fetch": "^3.3.2",
"rimraf": "^6.0.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=16.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/privilegemendes/amadeus-mcp-server-standalone.git"
},
"bugs": {
"url": "https://github.com/privilegemendes/amadeus-mcp-server-standalone/issues"
},
"homepage": "https://github.com/privilegemendes/amadeus-mcp-server-standalone#readme",
"publishConfig": {
"access": "public"
}
}
```
--------------------------------------------------------------------------------
/__tests__/caching.test.ts:
--------------------------------------------------------------------------------
```typescript
import { cache, cachedApiCall } from '../src/index';
// Mock console.error to avoid cluttering test output
console.error = jest.fn();
describe('Caching Functionality', () => {
beforeEach(() => {
// Clear the cache and console.error mocks before each test
jest.clearAllMocks();
// Get all cache keys and delete them
const keys = Object.keys(
(cache as { data: Record<string, unknown> }).data || {},
);
for (const key of keys) {
cache.del(key);
}
});
test('cachedApiCall should call the API when cache miss', async () => {
// Mock API call
const mockApiCall = jest.fn().mockResolvedValue({ data: 'test data' });
// Call the function
const result = await cachedApiCall('test-key', 60, mockApiCall);
// Assertions
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(result).toEqual({ data: 'test data' });
expect(console.error).toHaveBeenCalledWith(
'Cache miss for test-key, calling API...',
);
});
test('cachedApiCall should return cached data on cache hit', async () => {
// Prepare cache
const testData = { data: 'cached data' };
cache.set('test-key-2', testData);
// Mock API call - this should not be called
const mockApiCall = jest.fn();
// Call the function
const result = await cachedApiCall('test-key-2', 60, mockApiCall);
// Assertions
expect(mockApiCall).not.toHaveBeenCalled();
expect(result).toEqual(testData);
expect(console.error).toHaveBeenCalledWith('Cache hit for test-key-2');
});
test('cachedApiCall should handle API errors', async () => {
// Mock API call that throws
const mockError = new Error('API Error');
const mockApiCall = jest.fn().mockRejectedValue(mockError);
// Call the function and expect it to throw
await expect(cachedApiCall('test-key-3', 60, mockApiCall)).rejects.toThrow(
'API Error',
);
// Assertions
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
'Cache miss for test-key-3, calling API...',
);
expect(console.error).toHaveBeenCalledWith(
'API call failed for test-key-3:',
mockError,
);
});
});
```
--------------------------------------------------------------------------------
/__tests__/mcp-inspector.test.js:
--------------------------------------------------------------------------------
```javascript
const { spawn } = require('node:child_process');
const { setTimeout } = require('node:timers/promises');
// Helper function to get a future date string
const getFutureDate = (daysFromNow) => {
const date = new Date();
date.setDate(date.getDate() + daysFromNow);
return date.toISOString().split('T')[0]; // YYYY-MM-DD format
};
// Test cases for different tools
const testCases = [
{
tool: 'search-flights',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
adults: 1,
children: 0,
infants: 0,
nonStop: false,
currencyCode: 'USD',
maxResults: 5
}
},
{
tool: 'search-airports',
params: {
keyword: 'JFK',
maxResults: 5
}
},
{
tool: 'flight-price-analysis',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
currencyCode: 'USD'
}
},
{
tool: 'find-cheapest-dates',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
oneWay: true,
nonStop: false,
currencyCode: 'USD'
}
}
];
async function runInspectorTests() {
// Start the MCP server
const server = spawn('node', ['dist/cli.js']);
// Wait for server to start
await setTimeout(2000);
// Process server output
server.stdout.on('data', (data) => {
console.log(`Server: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server Error: ${data}`);
});
// Run each test case
for (const testCase of testCases) {
console.log(`\nTesting ${testCase.tool}...`);
// Create the inspector command
const inspector = spawn('npx', [
'@modelcontextprotocol/inspector',
'node',
'dist/cli.js',
'--tool',
testCase.tool,
'--params',
JSON.stringify(testCase.params)
]);
// Process inspector output
inspector.stdout.on('data', (data) => {
console.log(`Inspector: ${data}`);
});
inspector.stderr.on('data', (data) => {
console.error(`Inspector Error: ${data}`);
});
// Wait for inspector to complete
await new Promise((resolve) => {
inspector.on('close', (code) => {
console.log(`Inspector exited with code ${code}`);
resolve();
});
});
// Wait between tests to avoid rate limiting
await setTimeout(1000);
}
// Cleanup
server.kill();
}
// Run the tests
runInspectorTests().catch(console.error);
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const Amadeus = require('amadeus');
import dotenv from 'dotenv';
// Use require instead of import for node-cache
const NodeCache = require('node-cache');
// Define a type for our cache to make TypeScript happy
type TypedCache = {
get: <T>(key: string) => T | undefined;
set: <T>(key: string, value: T, ttl?: number) => boolean;
};
// Load environment variables
dotenv.config();
// Initialize Amadeus client only if credentials are available
export let amadeus = null;
if (process.env.AMADEUS_CLIENT_ID && process.env.AMADEUS_CLIENT_SECRET) {
amadeus = new Amadeus({
clientId: process.env.AMADEUS_CLIENT_ID,
clientSecret: process.env.AMADEUS_CLIENT_SECRET,
});
} else {
console.error('Warning: Amadeus credentials not found in environment variables');
}
// Create MCP server - FIXED VERSION
export const server = new McpServer({
name: 'amadeus-mcp-server',
version: '1.0.0'
});
// Create a cache instance
// Default TTL is 10 minutes (600 seconds)
export const cache = new NodeCache({
stdTTL: 600,
checkperiod: 120,
useClones: false,
}) as TypedCache;
/**
* Wrapper for Amadeus API calls with caching
* @param cacheKey - Key for caching
* @param ttl - Time to live in seconds
* @param apiCall - Function that returns a promise with the API call
* @returns Promise with API response
*/
export async function cachedApiCall<T>(
cacheKey: string,
ttl: number,
apiCall: () => Promise<T>,
): Promise<T> {
// Check if we have a cached response
const cachedResponse = cache.get<T>(cacheKey);
if (cachedResponse) {
console.error(`Cache hit for ${cacheKey}`);
return cachedResponse;
}
// If not cached, make the API call
console.error(`Cache miss for ${cacheKey}, calling API...`);
try {
const response = await apiCall();
// Cache the response with the specified TTL
cache.set<T>(cacheKey, response, ttl);
return response;
} catch (error: unknown) {
console.error(`API call failed for ${cacheKey}:`, error);
throw error;
}
}
// Start the server
export async function main() {
// Import all components to register tools, resources, and prompts
// This ensures they are properly registered with the server
await Promise.all([
import('./tools.js'),
import('./resources.js'),
import('./prompt.js')
]);
// Start server
console.error('Starting Amadeus Flight MCP Server...');
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Amadeus Flight MCP Server running');
}
// Only run main if this file is being run directly
if (require.main === module) {
main().catch((error: unknown) => {
console.error('Fatal error:', error);
process.exit(1);
});
}
```
--------------------------------------------------------------------------------
/__tests__/integration/airport-search.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Integration tests for Airport Search API
*/
const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
// Only run these tests if Amadeus credentials are available
describe('Airport Search API - Integration', () => {
// Set longer timeout for API calls (60 seconds to account for retries)
jest.setTimeout(60000);
conditionalTest(test, 'should find JFK airport', async () => {
// Skip if credentials not available (handled by conditionalTest)
expect(amadeus).not.toBeNull();
// Parameters for the search
const params = {
keyword: 'JFK',
subType: 'AIRPORT'
// Removed max parameter as it's causing issues with the API
};
try {
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.referenceData.locations.get(params)
);
// Basic validation
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
// If we searched for JFK, we should find it
const jfkAirport = response.data.find(item => item.iataCode === 'JFK');
expect(jfkAirport).toBeDefined();
// Match the actual format returned by the API (uppercase)
expect(jfkAirport.name).toContain('KENNEDY');
expect(jfkAirport.subType).toBe('AIRPORT');
console.log('Found JFK airport:', jfkAirport.name);
} catch (error) {
// Important to see the actual error if the API call fails
console.error('API Error:', error);
// Rethrow to fail the test
throw error;
}
});
conditionalTest(test, 'should find airports in New York', async () => {
expect(amadeus).not.toBeNull();
const params = {
keyword: 'New York',
subType: 'AIRPORT'
// Removed max parameter as it's causing issues with the API
};
try {
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.referenceData.locations.get(params)
);
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
expect(response.data.length).toBeGreaterThan(0);
// Should find some New York airports
const nyAirports = response.data.filter(item =>
item.address &&
(item.address.cityName === 'NEW YORK' ||
item.name.includes('NEW YORK') ||
item.name.includes('JFK') ||
item.name.includes('LAGUARDIA'))
);
expect(nyAirports.length).toBeGreaterThan(0);
console.log(`Found ${nyAirports.length} airports in New York:`,
nyAirports.map(a => a.name).join(', '));
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
});
```
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { server, amadeus } from './index.js';
// Load environment variables
dotenv.config();
// Start the server
async function main() {
// Check for Amadeus credentials
if (!amadeus) {
console.error('Error: Amadeus API client could not be initialized. Check your environment variables.');
process.exit(1);
}
// Set up Express app
const app = express();
// Configure CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (like mobile apps, curl, etc)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
const msg = `The CORS policy for this site does not allow access from ${origin}`;
return callback(new Error(msg), false);
}
return callback(null, true);
}
}));
app.use(express.json());
const PORT = process.env.PORT || 3000;
// Store active transports
const activeTransports = new Map();
// SSE endpoint
app.get('/sse', async (req, res) => {
console.error('New SSE connection requested');
// Generate a unique ID for this connection
const connectionId = Date.now().toString();
const transport = new SSEServerTransport('/messages', res);
activeTransports.set(connectionId, transport);
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
});
res.flushHeaders();
// Send the connection ID to the client
res.write(`data: ${JSON.stringify({ connectionId })}\n\n`);
await server.connect(transport);
req.on('close', () => {
console.error(`SSE connection ${connectionId} closed`);
activeTransports.delete(connectionId);
});
});
// Handle client-to-server messages
app.post('/messages', async (req, res) => {
const connectionId = req.query.connectionId as string;
const transport = activeTransports.get(connectionId);
if (!transport) {
return res.status(404).json({ error: 'Connection not found' });
}
await transport.handlePostMessage(req, res);
});
// Status endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
connections: activeTransports.size,
version: process.env.npm_package_version || '1.0.0'
});
});
// Start server
app.listen(PORT, () => {
console.error(`Amadeus Flight MCP Server running on port ${PORT}`);
console.error(`Environment: ${process.env.NODE_ENV || 'development'}`);
console.error(`Amadeus API client initialized: ${!!amadeus}`);
});
}
main().catch((error: unknown) => {
console.error('Fatal error:', error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/__tests__/cache-mock.test.js:
--------------------------------------------------------------------------------
```javascript
// Mock for node-cache
class MockNodeCache {
constructor() {
this.data = {};
}
get(key) {
return this.data[key];
}
set(key, value) {
this.data[key] = value;
return true;
}
del(key) {
if (this.data[key]) {
delete this.data[key];
return 1;
}
return 0;
}
}
// Mock for cachedApiCall function
describe('Cached API Call', () => {
let mockCache;
let cachedApiCall;
beforeEach(() => {
// Create a fresh mock cache for each test
mockCache = new MockNodeCache();
// Mock console.error
console.error = jest.fn();
// Implementation of cachedApiCall using our mock
cachedApiCall = async (cacheKey, ttl, apiCall) => {
// Check if we have a cached response
const cachedResponse = mockCache.get(cacheKey);
if (cachedResponse) {
console.error(`Cache hit for ${cacheKey}`);
return cachedResponse;
}
// If not cached, make the API call
console.error(`Cache miss for ${cacheKey}, calling API...`);
try {
const response = await apiCall();
// Cache the response with the specified TTL
mockCache.set(cacheKey, response);
return response;
} catch (error) {
console.error(`API call failed for ${cacheKey}:`, error);
throw error;
}
};
});
test('should call the API when cache misses', async () => {
// Mock API call
const mockApiCall = jest.fn().mockResolvedValue({ data: 'test data' });
// Call the function
const result = await cachedApiCall('test-key', 60, mockApiCall);
// Assertions
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(result).toEqual({ data: 'test data' });
expect(console.error).toHaveBeenCalledWith('Cache miss for test-key, calling API...');
});
test('should return cached data on cache hit', async () => {
// Prepare cache
const testData = { data: 'cached data' };
mockCache.set('test-key-2', testData);
// Mock API call - this should not be called
const mockApiCall = jest.fn();
// Call the function
const result = await cachedApiCall('test-key-2', 60, mockApiCall);
// Assertions
expect(mockApiCall).not.toHaveBeenCalled();
expect(result).toEqual(testData);
expect(console.error).toHaveBeenCalledWith('Cache hit for test-key-2');
});
test('should handle API errors', async () => {
// Mock API call that throws
const mockError = new Error('API Error');
const mockApiCall = jest.fn().mockRejectedValue(mockError);
// Call the function and expect it to throw
await expect(cachedApiCall('test-key-3', 60, mockApiCall)).rejects.toThrow('API Error');
// Assertions
expect(mockApiCall).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith('Cache miss for test-key-3, calling API...');
expect(console.error).toHaveBeenCalledWith('API call failed for test-key-3:', mockError);
});
});
```
--------------------------------------------------------------------------------
/__tests__/integration/setup.js:
--------------------------------------------------------------------------------
```javascript
/**
* Integration test setup for Amadeus API
*
* This module checks for the presence of Amadeus API credentials and initializes
* the API client if they are available. Tests can use the shouldRunIntegrationTests
* flag to determine if they should run or be skipped.
*/
// Load environment variables
require('dotenv').config();
// Initialize Amadeus client if credentials are available
const Amadeus = require('amadeus');
let amadeus = null;
let shouldRunIntegrationTests = false;
// Default delay between API calls to avoid rate limiting (in ms)
const API_CALL_DELAY = 1000;
// Maximum number of retries for rate limited requests
const MAX_RETRIES = 3;
/**
* Sleep utility function
* @param {number} ms - Milliseconds to sleep
* @returns {Promise} - Promise that resolves after the specified time
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Make an API call with retry logic for rate limiting
* @param {Function} apiCallFn - Function that makes the API call
* @param {number} retries - Number of retries left
* @param {number} delay - Delay between retries in ms
* @returns {Promise} - Promise that resolves with the API response
*/
async function makeApiCallWithRetry(apiCallFn, retries = MAX_RETRIES, delay = API_CALL_DELAY) {
try {
// Add a small delay before each API call to help avoid rate limiting
await sleep(delay);
return await apiCallFn();
} catch (error) {
// Check if it's a rate limiting error (HTTP 429)
if (error.response && error.response.statusCode === 429 && retries > 0) {
console.log(`Rate limited. Retrying after ${delay * 2}ms. Retries left: ${retries}`);
// Exponential backoff - double the delay for each retry
await sleep(delay * 2);
return makeApiCallWithRetry(apiCallFn, retries - 1, delay * 2);
}
// If not rate limited or out of retries, rethrow
throw error;
}
}
try {
// Check if required credentials are available
if (process.env.AMADEUS_CLIENT_ID && process.env.AMADEUS_CLIENT_SECRET) {
amadeus = new Amadeus({
clientId: process.env.AMADEUS_CLIENT_ID,
clientSecret: process.env.AMADEUS_CLIENT_SECRET
});
shouldRunIntegrationTests = true;
console.log('Amadeus API credentials found. Integration tests will run.');
} else {
console.log('Amadeus API credentials not found. Integration tests will be skipped.');
console.log('To run integration tests, set AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET in your .env file.');
}
} catch (error) {
console.error('Failed to initialize Amadeus client:', error);
}
/**
* Utility function to conditionally skip tests if credentials aren't available
* @param {Function} testFn - The Jest test function (test or it)
* @param {string} testName - The name of the test
* @param {Function} testCallback - The test callback function
*/
function conditionalTest(testFn, testName, testCallback) {
if (shouldRunIntegrationTests) {
testFn(testName, testCallback);
} else {
testFn.skip(testName, () => {
console.log(`Test "${testName}" skipped due to missing credentials`);
});
}
}
module.exports = {
amadeus,
shouldRunIntegrationTests,
conditionalTest,
sleep,
makeApiCallWithRetry
};
```
--------------------------------------------------------------------------------
/scripts/test-tools.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
const { spawn } = require('node:child_process');
const readline = require('node:readline');
// Helper function to get a future date string
const getFutureDate = (daysFromNow) => {
const date = new Date();
date.setDate(date.getDate() + daysFromNow);
return date.toISOString().split('T')[0]; // YYYY-MM-DD format
};
// Predefined test cases
const testCases = {
'1': {
name: 'Search flights (JFK to LAX)',
tool: 'search-flights',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
adults: 1,
children: 0,
infants: 0,
nonStop: false,
currencyCode: 'USD',
maxResults: 5
}
},
'2': {
name: 'Search airports (JFK)',
tool: 'search-airports',
params: {
keyword: 'JFK',
maxResults: 5
}
},
'3': {
name: 'Price analysis (JFK to LAX)',
tool: 'flight-price-analysis',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
currencyCode: 'USD'
}
},
'4': {
name: 'Find cheapest dates (JFK to LAX)',
tool: 'find-cheapest-dates',
params: {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: getFutureDate(90),
oneWay: true,
nonStop: false,
currencyCode: 'USD'
}
},
'5': {
name: 'Round-trip flights (BOS to LHR)',
tool: 'search-flights',
params: {
originLocationCode: 'BOS',
destinationLocationCode: 'LHR',
departureDate: getFutureDate(90),
returnDate: getFutureDate(97),
adults: 1,
children: 0,
infants: 0,
nonStop: false,
currencyCode: 'USD',
maxResults: 5
}
}
};
// Create readline interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Function to display menu
function displayMenu() {
console.log('\nAvailable test cases:');
for (const [key, test] of Object.entries(testCases)) {
console.log(`${key}. ${test.name}`);
}
console.log('q. Quit');
}
// Function to run a test case
function runTest(testCase) {
console.log(`\nRunning: ${testCase.name}`);
console.log('Parameters:', JSON.stringify(testCase.params, null, 2));
const inspector = spawn('npx', [
'@modelcontextprotocol/inspector',
'node',
'dist/cli.js',
'--tool',
testCase.tool,
'--params',
JSON.stringify(testCase.params)
]);
inspector.stdout.on('data', (data) => {
console.log(`\n${data}`);
});
inspector.stderr.on('data', (data) => {
console.error(`\nError: ${data}`);
});
inspector.on('close', (code) => {
console.log(`\nTest completed with code ${code}`);
displayMenu();
askForInput();
});
}
// Function to ask for input
function askForInput() {
rl.question('\nEnter the number of the test case to run (or q to quit): ', (answer) => {
if (answer.toLowerCase() === 'q') {
rl.close();
return;
}
const testCase = testCases[answer];
if (testCase) {
runTest(testCase);
} else {
console.log('Invalid selection. Please try again.');
displayMenu();
askForInput();
}
});
}
// Start the program
console.log('Amadeus MCP Server Tool Tester');
console.log('=============================');
displayMenu();
askForInput();
```
--------------------------------------------------------------------------------
/__tests__/prompts.test.ts:
--------------------------------------------------------------------------------
```typescript
import { server } from '../src/index';
describe('MCP Prompts', () => {
// Helper function to get a prompt handler
const getPromptHandler = (promptName: string) => {
const prompt = server.prompts.find(
(p: { meta?: { name: string } }) => p.meta?.name === promptName,
);
return prompt?.handler;
};
test('analyze-flight-prices prompt returns correct message structure', async () => {
const promptHandler = getPromptHandler('analyze-flight-prices');
expect(promptHandler).toBeDefined();
if (promptHandler) {
const result = await promptHandler({
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: '2023-01-01',
returnDate: '2023-01-10',
});
expect(result).toHaveProperty('messages');
expect(result.messages).toHaveLength(1);
expect(result.messages[0]).toHaveProperty('role', 'user');
expect(result.messages[0].content).toHaveProperty('type', 'text');
// Check that the text includes the parameters we provided
const text = result.messages[0].content.text;
expect(text).toContain('JFK');
expect(text).toContain('LAX');
expect(text).toContain('2023-01-01');
expect(text).toContain('2023-01-10');
expect(text).toContain('flight-price-analysis tool');
expect(text).toContain('search-flights tool');
}
});
test('find-best-deals prompt returns correct message structure', async () => {
const promptHandler = getPromptHandler('find-best-deals');
expect(promptHandler).toBeDefined();
if (promptHandler) {
const result = await promptHandler({
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: '2023-01-01',
returnDate: '2023-01-10',
travelClass: 'BUSINESS',
});
expect(result).toHaveProperty('messages');
expect(result.messages).toHaveLength(1);
expect(result.messages[0]).toHaveProperty('role', 'user');
expect(result.messages[0].content).toHaveProperty('type', 'text');
// Check that the text includes the parameters we provided
const text = result.messages[0].content.text;
expect(text).toContain('JFK');
expect(text).toContain('LAX');
expect(text).toContain('2023-01-01');
expect(text).toContain('2023-01-10');
expect(text).toContain('BUSINESS class');
expect(text).toContain('search-flights tool');
}
});
test('plan-multi-city-trip prompt returns correct message structure', async () => {
const promptHandler = getPromptHandler('plan-multi-city-trip');
expect(promptHandler).toBeDefined();
if (promptHandler) {
const result = await promptHandler({
cities: 'LAX, SFO, LAS',
startDate: '2023-01-01',
endDate: '2023-01-15',
homeAirport: 'JFK',
});
expect(result).toHaveProperty('messages');
expect(result.messages).toHaveLength(1);
expect(result.messages[0]).toHaveProperty('role', 'user');
expect(result.messages[0].content).toHaveProperty('type', 'text');
// Check that the text includes the parameters we provided
const text = result.messages[0].content.text;
expect(text).toContain('LAX, SFO, LAS');
expect(text).toContain('JFK');
expect(text).toContain('2023-01-01');
expect(text).toContain('2023-01-15');
expect(text).toContain('search-airports tool');
expect(text).toContain('search-flights tool');
}
});
});
```
--------------------------------------------------------------------------------
/src/naturalLanguageHandler.ts:
--------------------------------------------------------------------------------
```typescript
import { analyzeQuery } from './queryAnalyzer.js';
import { processQuery } from './queryProcessor.js';
import { server } from './index.js';
import { z } from 'zod';
// Interface for the natural language response
interface NLResponse {
summary: string;
details: any;
suggestedActions: string[];
followUpQuestions: string[];
}
// Format the response in natural language
function formatResponse(
queryResult: any,
analyzedQuery: any
): NLResponse {
const response: NLResponse = {
summary: '',
details: queryResult.mainResult,
suggestedActions: [],
followUpQuestions: queryResult.suggestedFollowUp || [],
};
// Generate summary based on query type
switch (analyzedQuery.type) {
case 'inspiration':
response.summary = `I've found some interesting destinations you might like to visit${
analyzedQuery.timeFrame.value ? ` during ${analyzedQuery.timeFrame.value}` : ''
}${
analyzedQuery.budget ? ` within your budget of ${analyzedQuery.budget.amount}` : ''
}.`;
break;
case 'specific_route':
response.summary = `Here are the best flight options from ${analyzedQuery.origin?.raw} to ${
analyzedQuery.destinations[0]?.raw
}${analyzedQuery.timeFrame.value ? ` on ${analyzedQuery.timeFrame.value}` : ''}.`;
break;
case 'multi_city':
response.summary = `I've planned a multi-city trip visiting ${
analyzedQuery.destinations.map(d => d.raw).join(', ')
}${analyzedQuery.duration ? ` over ${analyzedQuery.duration.value} ${analyzedQuery.duration.type}` : ''}.`;
break;
case 'flexible_value':
response.summary = `I've found the most economical travel options for your trip${
analyzedQuery.destinations.length > 0 ? ` to ${analyzedQuery.destinations[0].raw}` : ''
}.`;
break;
}
// Generate suggested actions based on results
if (queryResult.supplementaryResults) {
response.suggestedActions = [
'Compare prices across different dates',
'Check alternative airports',
'Set up price alerts',
'View detailed flight information',
];
}
return response;
}
// Main handler for natural language queries
export async function handleNaturalLanguageQuery(
query: string
): Promise<NLResponse> {
try {
// Step 1: Analyze the natural language query
const analyzed = await analyzeQuery(query);
// Step 2: Process the analyzed query with appropriate tools
const result = await processQuery(analyzed);
// Step 3: Format the response in natural language
const response = formatResponse(result, analyzed);
// Add confidence-based disclaimer if needed
if (analyzed.confidence < 0.8) {
response.summary = `I'm not entirely sure, but ${response.summary.toLowerCase()} Please let me know if this isn't what you were looking for.`;
}
return response;
} catch (error) {
console.error('Error handling natural language query:', error);
throw error;
}
}
// Register the natural language prompt
server.prompt(
'natural-language-travel',
'Handle natural language travel queries',
{
query: z.string().describe('The natural language travel query'),
},
async ({ query }: { query: string }) => {
const response = await handleNaturalLanguageQuery(query);
return {
messages: [
{
role: 'assistant',
content: {
type: 'text',
text: `${response.summary}\n\n${
response.followUpQuestions.length > 0
? `To help me provide better results, could you please clarify:\n${response.followUpQuestions
.map(q => `- ${q}`)
.join('\n')}\n\n`
: ''
}${
response.suggestedActions.length > 0
? `You can also:\n${response.suggestedActions
.map(a => `- ${a}`)
.join('\n')}`
: ''
}`,
},
},
],
};
},
);
```
--------------------------------------------------------------------------------
/__tests__/integration/flight-search.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Integration tests for Flight Search API
*/
const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
describe('Flight Search API - Integration', () => {
// Set longer timeout for API calls (60 seconds to account for retries)
jest.setTimeout(60000);
// Helper function to get a future date string
const getFutureDate = (daysFromNow) => {
const date = new Date();
date.setDate(date.getDate() + daysFromNow);
return date.toISOString().split('T')[0]; // YYYY-MM-DD format
};
conditionalTest(test, 'should find flights from JFK to LAX', async () => {
expect(amadeus).not.toBeNull();
// Set dates for 90 days from now to ensure it's in the future
const departureDateStr = getFutureDate(90);
// Parameters for the flight search
const params = {
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: departureDateStr,
adults: 1,
max: 5, // Limit to 5 results for faster testing
};
try {
console.log(`Searching for flights from JFK to LAX on ${departureDateStr}`);
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.shopping.flightOffersSearch.get(params)
);
// Basic validation
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
// We should find at least one flight
expect(response.data.length).toBeGreaterThan(0);
// First flight should depart from JFK
const firstOffer = response.data[0];
const firstSegment = firstOffer.itineraries[0].segments[0];
expect(firstSegment.departure.iataCode).toBe('JFK');
expect(firstSegment.arrival).toBeDefined();
console.log(`Found ${response.data.length} flights from JFK`);
console.log(`First flight: ${firstOffer.price.total} ${firstOffer.price.currency}`);
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
conditionalTest(test, 'should find round-trip flights', async () => {
expect(amadeus).not.toBeNull();
// Set dates for 90 and 97 days from now (one week trip)
const departureDateStr = getFutureDate(90);
const returnDateStr = getFutureDate(97);
// Parameters for the flight search
const params = {
originLocationCode: 'BOS',
destinationLocationCode: 'LHR',
departureDate: departureDateStr,
returnDate: returnDateStr,
adults: 1,
max: 5, // Limit to 5 results for faster testing
};
try {
console.log(`Searching for round-trip flights from BOS to LHR on ${departureDateStr} returning ${returnDateStr}`);
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.shopping.flightOffersSearch.get(params)
);
// Basic validation
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
// We should find at least one flight
expect(response.data.length).toBeGreaterThan(0);
// For round-trip, we should have 2 itineraries
const firstOffer = response.data[0];
expect(firstOffer.itineraries.length).toBe(2);
// Outbound should be BOS to LHR
const outbound = firstOffer.itineraries[0];
const outSegment = outbound.segments[0];
expect(outSegment.departure.iataCode).toBe('BOS');
// Return should be LHR to BOS
const returnFlight = firstOffer.itineraries[1];
const returnSegment = returnFlight.segments[returnFlight.segments.length - 1];
expect(returnSegment.arrival.iataCode).toBe('BOS');
console.log(`Found ${response.data.length} round-trip flights from BOS to LHR`);
console.log(`Price: ${firstOffer.price.total} ${firstOffer.price.currency}`);
} catch (error) {
console.error('API Error:', error);
throw error;
}
});
});
```
--------------------------------------------------------------------------------
/src/queryProcessor.ts:
--------------------------------------------------------------------------------
```typescript
import { type AnalyzedQuery, TravelQueryType } from './queryAnalyzer.js';
import { server } from './index.js';
// Interface for tool selection and parameter mapping
interface ToolMapping {
primaryTool: string;
secondaryTools?: string[];
parameterMap: (query: AnalyzedQuery) => Promise<Record<string, any>>;
}
// Map query types to appropriate tools and parameter mappings
const toolMappings: Record<TravelQueryType, ToolMapping> = {
[TravelQueryType.INSPIRATION]: {
primaryTool: 'discover-destinations',
secondaryTools: ['search-flights', 'search-airports'],
async parameterMap(query) {
return {
originLocationCode: query.origin?.code || '',
maxPrice: query.budget?.amount,
departureDate: query.timeFrame.value,
tripDuration: query.duration?.value.toString(),
};
},
},
[TravelQueryType.SPECIFIC_ROUTE]: {
primaryTool: 'find-best-deals',
secondaryTools: ['analyze-flight-prices', 'find-cheapest-travel-dates'],
async parameterMap(query) {
return {
originLocationCode: query.origin?.code || '',
destinationLocationCode: query.destinations[0]?.code || '',
departureDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[0]
: query.timeFrame.value,
returnDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[1]
: undefined,
travelClass: query.preferences?.class?.toUpperCase(),
};
},
},
[TravelQueryType.MULTI_CITY]: {
primaryTool: 'plan-multi-city-trip',
secondaryTools: ['search-flights', 'airport-routes'],
async parameterMap(query) {
const cities = query.destinations.map(dest => dest.code).join(',');
return {
cities,
startDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[0]
: query.timeFrame.value,
endDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[1]
: '',
homeAirport: query.origin?.code || '',
};
},
},
[TravelQueryType.FLEXIBLE_VALUE]: {
primaryTool: 'find-cheapest-travel-dates',
secondaryTools: ['analyze-flight-prices'],
async parameterMap(query) {
return {
originLocationCode: query.origin?.code || '',
destinationLocationCode: query.destinations[0]?.code || '',
earliestDepartureDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[0]
: query.timeFrame.value,
latestDepartureDate: Array.isArray(query.timeFrame.value)
? query.timeFrame.value[1]
: '',
tripDuration: query.duration?.value.toString(),
};
},
},
};
// Process the analyzed query and call appropriate tools
export async function processQuery(
analyzedQuery: AnalyzedQuery
): Promise<{
mainResult: any;
supplementaryResults?: Record<string, any>;
confidence: number;
suggestedFollowUp?: string[];
}> {
const mapping = toolMappings[analyzedQuery.type];
if (!mapping) {
throw new Error(`No tool mapping found for query type: ${analyzedQuery.type}`);
}
try {
// Map the analyzed query to tool parameters
const params = await mapping.parameterMap(analyzedQuery);
// Call the primary tool
const mainResult = await server.prompt(mapping.primaryTool, () => params);
// Call secondary tools if needed and if confidence is high enough
const supplementaryResults: Record<string, any> = {};
if (mapping.secondaryTools && analyzedQuery.confidence > 0.7) {
for (const tool of mapping.secondaryTools) {
try {
supplementaryResults[tool] = await server.prompt(tool, () => params);
} catch (error) {
console.error(`Error running secondary tool ${tool}:`, error);
}
}
}
// Generate follow-up suggestions based on ambiguities or missing information
const suggestedFollowUp = generateFollowUpQuestions(analyzedQuery);
return {
mainResult,
supplementaryResults: Object.keys(supplementaryResults).length > 0
? supplementaryResults
: undefined,
confidence: analyzedQuery.confidence,
suggestedFollowUp,
};
} catch (error) {
console.error('Error processing query:', error);
throw error;
}
}
// Generate follow-up questions based on analysis
function generateFollowUpQuestions(query: AnalyzedQuery): string[] {
const questions: string[] = [];
// Check for missing or ambiguous information
if (!query.origin?.code) {
questions.push('Which city would you like to depart from?');
}
if (query.timeFrame.isFlexible) {
questions.push('Do you have specific dates in mind for your travel?');
}
if (!query.budget && query.type !== TravelQueryType.SPECIFIC_ROUTE) {
questions.push('Do you have a budget in mind for this trip?');
}
if (!query.duration && query.type !== TravelQueryType.SPECIFIC_ROUTE) {
questions.push('How long would you like to travel for?');
}
if (query.ambiguities) {
questions.push(...query.ambiguities);
}
return questions;
}
// Example usage:
// const query = "I want to go somewhere warm in December for about a week";
// const analyzed = await analyzeQuery(query);
// const result = await processQuery(analyzed);
```
--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------
```typescript
// Schema resources
import { server } from './index.js';
server.resource(
'flight-offer-schema',
'schema://flight-offers',
async (uri) => {
const schema = {
type: 'Flight Offer',
properties: {
type: 'The type of the offer (e.g., flight-offer)',
id: 'Unique identifier for the offer',
source: 'The source of the offer',
instantTicketingRequired: 'Whether instant ticketing is required',
nonHomogeneous: 'Whether the offer is non-homogeneous',
oneWay: 'Whether the offer is one-way',
lastTicketingDate: 'The last date for ticketing',
numberOfBookableSeats: 'Number of bookable seats',
itineraries: 'Array of flight segments',
price: 'Price information including total and currency',
pricingOptions: 'Options related to pricing',
validatingAirlineCodes: 'Codes of validating airlines',
travelerPricings: 'Pricing information per traveler',
},
};
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(schema, null, 2),
mimeType: 'application/json',
},
],
};
},
);
// Airport schema
server.resource('airport-schema', 'schema://airports', async (uri) => {
const schema = {
type: 'Airport',
properties: {
iataCode: 'IATA three-letter airport code',
name: 'Full name of the airport',
cityCode: 'City code',
cityName: 'City name',
countryCode: 'Two-letter country code',
countryName: 'Country name',
latitude: 'Geographic latitude',
longitude: 'Geographic longitude',
timezone: 'Timezone of the airport',
},
};
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(schema, null, 2),
mimeType: 'application/json',
},
],
};
});
// Flight Inspiration schema
server.resource(
'flight-inspiration-schema',
'schema://flight-inspiration',
async (uri) => {
const schema = {
type: 'Flight Inspiration',
properties: {
type: 'Type of the result',
origin: 'IATA code of the origin city/airport',
destination: 'IATA code of the destination city/airport',
departureDate: 'Date of departure',
returnDate: 'Date of return (for round trips)',
price: {
description: 'Price information',
properties: {
total: 'Total price of the flight',
},
},
links: {
description: 'Related links',
properties: {
flightDates: 'Link to flight dates',
flightOffers: 'Link to flight offers',
},
},
},
};
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(schema, null, 2),
mimeType: 'application/json',
},
],
};
},
);
// Airport Routes schema
server.resource(
'airport-routes-schema',
'schema://airport-routes',
async (uri) => {
const schema = {
type: 'Airport Route',
properties: {
type: 'Type of the route',
subtype: 'Subtype of the route',
name: 'Name of the destination',
iataCode: 'IATA code of the destination',
distance: {
description: 'Distance information',
properties: {
value: 'Numeric value of the distance',
unit: 'Unit of measurement',
},
},
analytics: {
description: 'Analytics information',
properties: {
flights: {
description: 'Flight statistics',
properties: {
score: 'Flight frequency score',
},
},
travelers: {
description: 'Traveler statistics',
properties: {
score: 'Traveler volume score',
},
},
},
},
},
};
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(schema, null, 2),
mimeType: 'application/json',
},
],
};
},
);
// Nearest Airport schema
server.resource(
'nearest-airport-schema',
'schema://nearest-airports',
async (uri) => {
const schema = {
type: 'Nearest Airport',
properties: {
type: 'Type of the location',
subtype: 'Subtype of the location (e.g., AIRPORT)',
name: 'Name of the airport',
detailedName: 'Detailed name including location',
iataCode: 'IATA code of the airport',
distance: {
description: 'Distance from search point',
properties: {
value: 'Numeric value of the distance',
unit: 'Unit of measurement',
},
},
analytics: {
description: 'Analytics information',
properties: {
flights: {
description: 'Flight statistics',
properties: {
score: 'Flight frequency score',
},
},
travelers: {
description: 'Traveler statistics',
properties: {
score: 'Traveler volume score',
},
},
},
},
},
};
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(schema, null, 2),
mimeType: 'application/json',
},
],
};
},
);
```
--------------------------------------------------------------------------------
/__tests__/integration/price-analysis.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Integration tests for Flight Price Analysis API
*/
const { amadeus, conditionalTest, makeApiCallWithRetry } = require('./setup');
describe('Flight Price Analysis API - Integration', () => {
// Set longer timeout for API calls (60 seconds to account for retries)
jest.setTimeout(60000);
conditionalTest(test, 'should get price analysis for JFK to LAX route', async () => {
expect(amadeus).not.toBeNull();
// Set dates for 30 days from now
const departureDate = new Date();
departureDate.setDate(departureDate.getDate() + 30);
const departureDateStr = departureDate.toISOString().split('T')[0]; // YYYY-MM-DD format
// Parameters for the price analysis - using correct parameter names
const params = {
originIataCode: 'JFK',
destinationIataCode: 'LAX',
departureDate: departureDateStr,
currencyCode: 'USD'
};
try {
console.log(`Getting price analysis for JFK to LAX on ${departureDateStr}`);
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.analytics.itineraryPriceMetrics.get(params)
);
// Basic validation
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
// Should find price data
expect(response.data.length).toBeGreaterThan(0);
// Check the first result - the API response format may have changed
const analysis = response.data[0];
// The origin might be an object with iataCode instead of a string
if (typeof analysis.origin === 'object' && analysis.origin.iataCode) {
expect(analysis.origin.iataCode).toBe('JFK');
} else {
expect(analysis.origin).toBe('JFK');
}
// Similarly, check for destination
if (typeof analysis.destination === 'object' && analysis.destination.iataCode) {
expect(analysis.destination.iataCode).toBe('LAX');
} else {
expect(analysis.destination).toBe('LAX');
}
// The departure date should still match
expect(analysis.departureDate).toBe(departureDateStr);
expect(analysis.priceMetrics).toBeDefined();
expect(Array.isArray(analysis.priceMetrics)).toBe(true);
expect(analysis.priceMetrics.length).toBeGreaterThan(0);
// Check price metrics structure
const firstMetric = analysis.priceMetrics[0];
expect(firstMetric.amount).toBeDefined();
expect(firstMetric.quartileRanking).toBeDefined();
console.log(`Price metrics: ${analysis.priceMetrics.map(m => `${m.quartileRanking}: ${m.amount}`).join(', ')}`);
} catch (error) {
// The price analysis API might not have data for all routes/dates
// If we get a 404, we'll just skip this test
if (error.code === 404 || error.response?.statusCode === 404) {
console.log('Price analysis data not available for this route/date, skipping test');
return; // Skip but don't fail
}
console.error('API Error:', error);
throw error;
}
});
conditionalTest(test, 'should get price analysis for a popular route (NYC to LON)', async () => {
expect(amadeus).not.toBeNull();
// Set dates for 45 days from now (more likely to have data for popular routes)
const departureDate = new Date();
departureDate.setDate(departureDate.getDate() + 45);
const departureDateStr = departureDate.toISOString().split('T')[0];
// Parameters for the price analysis - using correct parameter names
const params = {
originIataCode: 'NYC', // New York City (all airports)
destinationIataCode: 'LON', // London (all airports)
departureDate: departureDateStr,
currencyCode: 'USD'
};
try {
console.log(`Getting price analysis for NYC to LON on ${departureDateStr}`);
// Use the makeApiCallWithRetry helper to handle rate limiting
const response = await makeApiCallWithRetry(() =>
amadeus.analytics.itineraryPriceMetrics.get(params)
);
// Basic validation
expect(response).toBeDefined();
expect(response.data).toBeDefined();
expect(Array.isArray(response.data)).toBe(true);
// For this test, let's skip the expectations if no data is returned
// Popular routes should have data, but different test environments may return different results
if (response.data.length === 0) {
console.log('No price analysis data found for NYC to LON, skipping validation');
return;
}
// Log the results
for (const analysis of response.data) {
// Get origin and destination values, handling both object and string formats
const origin = typeof analysis.origin === 'object' ? analysis.origin.iataCode : analysis.origin;
const destination = typeof analysis.destination === 'object' ? analysis.destination.iataCode : analysis.destination;
console.log(`Analysis for ${origin} to ${destination} on ${analysis.departureDate}:`);
for (const metric of analysis.priceMetrics) {
console.log(` ${metric.quartileRanking}: ${metric.amount}`);
}
}
} catch (error) {
// If we get a 404, we'll just skip this test
if (error.code === 404 || error.response?.statusCode === 404) {
console.log('Price analysis data not available for this route/date, skipping test');
return; // Skip but don't fail
}
console.error('API Error:', error);
throw error;
}
});
});
```
--------------------------------------------------------------------------------
/__tests__/tools.test.ts:
--------------------------------------------------------------------------------
```typescript
import { amadeus } from '../src/index';
import { server } from '../src/index';
// Mock the Amadeus API methods
jest.mock('amadeus', () => {
return jest.fn().mockImplementation(() => {
return {
shopping: {
flightOffers: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'flight-offer',
id: '1',
price: { total: '200.00', currency: 'USD' },
itineraries: [
{
duration: 'PT5H30M',
segments: [
{
departure: {
iataCode: 'JFK',
at: '2023-01-01T08:00:00',
},
arrival: { iataCode: 'LAX', at: '2023-01-01T13:30:00' },
carrierCode: 'AA',
number: '123',
},
],
},
],
validatingAirlineCodes: ['AA'],
numberOfBookableSeats: 10,
},
],
}),
},
},
referenceData: {
locations: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'location',
subType: 'AIRPORT',
name: 'John F Kennedy International Airport',
iataCode: 'JFK',
city: 'NEW YORK',
countryCode: 'US',
},
],
}),
},
},
analytics: {
itineraryPriceMetrics: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'analytics',
origin: 'JFK',
destination: 'LAX',
departureDate: '2023-01-01',
priceMetrics: [
{
amount: '200.00',
quartileRanking: 'MINIMUM',
},
],
},
],
}),
},
},
};
});
});
// Mock console.error to avoid cluttering test output
console.error = jest.fn();
describe('Amadeus API Tools', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// Helper function to get a tool handler
const getToolHandler = (toolName: string) => {
const tool = server.tools.find(
(t: { meta?: { name: string } }) => t.meta?.name === toolName,
);
return tool?.handler;
};
test('search-flights tool returns formatted flight data', async () => {
const searchFlightsHandler = getToolHandler('search-flights');
expect(searchFlightsHandler).toBeDefined();
if (searchFlightsHandler) {
const result = await searchFlightsHandler({
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: '2023-01-01',
adults: 1,
children: 0,
infants: 0,
nonStop: false,
currencyCode: 'USD',
maxResults: 10,
});
expect(result).toHaveProperty('content');
expect(result.content[0]).toHaveProperty('text');
// Parse the JSON text to check the formatted data
const formattedData = JSON.parse(result.content[0].text);
expect(formattedData).toHaveLength(1);
expect(formattedData[0]).toHaveProperty('price', '200.00 USD');
expect(formattedData[0]).toHaveProperty('bookableSeats', 10);
expect(formattedData[0]).toHaveProperty('airlines', 'AA');
expect(formattedData[0]).toHaveProperty('itineraries');
expect(formattedData[0].itineraries[0]).toHaveProperty(
'type',
'Outbound',
);
expect(formattedData[0].itineraries[0]).toHaveProperty(
'duration',
'5h 30m',
);
expect(formattedData[0].itineraries[0]).toHaveProperty(
'stops',
'Non-stop',
);
}
});
test('search-airports tool returns airport data', async () => {
const searchAirportsHandler = getToolHandler('search-airports');
expect(searchAirportsHandler).toBeDefined();
if (searchAirportsHandler) {
const result = await searchAirportsHandler({
keyword: 'JFK',
maxResults: 10,
});
expect(result).toHaveProperty('content');
expect(result.content[0]).toHaveProperty('text');
// Parse the JSON text to check the data
const data = JSON.parse(result.content[0].text);
expect(data).toHaveLength(1);
expect(data[0]).toHaveProperty('iataCode', 'JFK');
expect(data[0]).toHaveProperty(
'name',
'John F Kennedy International Airport',
);
}
});
test('flight-price-analysis tool returns price metrics', async () => {
const priceAnalysisHandler = getToolHandler('flight-price-analysis');
expect(priceAnalysisHandler).toBeDefined();
if (priceAnalysisHandler) {
const result = await priceAnalysisHandler({
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: '2023-01-01',
currencyCode: 'USD',
});
expect(result).toHaveProperty('content');
expect(result.content[0]).toHaveProperty('text');
// Parse the JSON text to check the data
const data = JSON.parse(result.content[0].text);
expect(data).toHaveLength(1);
expect(data[0]).toHaveProperty('origin', 'JFK');
expect(data[0]).toHaveProperty('destination', 'LAX');
expect(data[0]).toHaveProperty('priceMetrics');
expect(data[0].priceMetrics[0]).toHaveProperty('amount', '200.00');
}
});
});
```
--------------------------------------------------------------------------------
/__tests__/amadeus-mock.test.js:
--------------------------------------------------------------------------------
```javascript
// Mock for Amadeus API
const mockAmadeus = {
shopping: {
flightOffers: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'flight-offer',
id: '1',
price: { total: '200.00', currency: 'USD' },
itineraries: [
{
duration: 'PT5H30M',
segments: [
{
departure: { iataCode: 'JFK', at: '2023-01-01T08:00:00' },
arrival: { iataCode: 'LAX', at: '2023-01-01T13:30:00' },
carrierCode: 'AA',
number: '123'
}
]
}
],
validatingAirlineCodes: ['AA'],
numberOfBookableSeats: 10
}
]
})
}
},
referenceData: {
locations: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'location',
subType: 'AIRPORT',
name: 'John F Kennedy International Airport',
iataCode: 'JFK',
city: 'NEW YORK',
countryCode: 'US'
}
]
})
}
},
analytics: {
itineraryPriceMetrics: {
get: jest.fn().mockResolvedValue({
data: [
{
type: 'analytics',
origin: 'JFK',
destination: 'LAX',
departureDate: '2023-01-01',
priceMetrics: [
{
amount: '200.00',
quartileRanking: 'MINIMUM'
}
]
}
]
})
}
}
};
// Mock the tool handler implementation
describe('Amadeus API Tools', () => {
// Mock Search Flights Tool
test('search-flights tool returns formatted flight data', async () => {
// Mock implementation of search-flights tool handler
const searchFlightsHandler = async (params) => {
const response = await mockAmadeus.shopping.flightOffers.get();
const formattedResults = response.data.map((offer) => {
const {
price,
itineraries,
validatingAirlineCodes,
numberOfBookableSeats,
} = offer;
// Format itineraries with more details
const formattedItineraries = itineraries.map((itinerary, idx) => {
// Calculate duration in hours and minutes
const durationStr = itinerary.duration.slice(2, -1); // Remove 'PT' and 'M'
let hours = 0;
let minutes = 0;
if (durationStr.includes('H')) {
const parts = durationStr.split('H');
hours = Number.parseInt(parts[0]);
if (parts[1]) {
minutes = Number.parseInt(parts[1]);
}
} else {
minutes = Number.parseInt(durationStr);
}
const formattedDuration = `${hours}h ${minutes}m`;
// Format stops
const numStops = itinerary.segments.length - 1;
const stopsText = numStops === 0 ? 'Non-stop' : `${numStops} stop${numStops > 1 ? 's' : ''}`;
return {
type: idx === 0 ? 'Outbound' : 'Return',
duration: formattedDuration,
stops: stopsText,
segments: itinerary.segments.map(segment =>
`${segment.departure.iataCode} → ${segment.arrival.iataCode} - ${segment.carrierCode}${segment.number}`
).join(' | ')
};
});
return {
price: `${price.total} ${price.currency}`,
bookableSeats: numberOfBookableSeats || 'Unknown',
airlines: validatingAirlineCodes.join(', '),
itineraries: formattedItineraries,
};
});
return {
content: [
{
type: 'text',
text: JSON.stringify(formattedResults, null, 2)
}
]
};
};
// Call the handler with test parameters
const result = await searchFlightsHandler({
originLocationCode: 'JFK',
destinationLocationCode: 'LAX',
departureDate: '2023-01-01',
adults: 1,
children: 0,
infants: 0,
nonStop: false,
currencyCode: 'USD',
maxResults: 10
});
// Parse the JSON text to check the formatted data
const formattedData = JSON.parse(result.content[0].text);
// Assertions
expect(formattedData).toHaveLength(1);
expect(formattedData[0]).toHaveProperty('price', '200.00 USD');
expect(formattedData[0]).toHaveProperty('bookableSeats', 10);
expect(formattedData[0]).toHaveProperty('airlines', 'AA');
expect(formattedData[0].itineraries[0]).toHaveProperty('type', 'Outbound');
expect(formattedData[0].itineraries[0]).toHaveProperty('duration', '5h 30m');
expect(formattedData[0].itineraries[0]).toHaveProperty('stops', 'Non-stop');
});
// Mock Airport Search Tool
test('search-airports tool returns airport data', async () => {
// Mock implementation of search-airports tool handler
const searchAirportsHandler = async (params) => {
const response = await mockAmadeus.referenceData.locations.get();
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2)
}
]
};
};
// Call the handler with test parameters
const result = await searchAirportsHandler({
keyword: 'JFK',
maxResults: 10
});
// Parse the JSON text to check the data
const data = JSON.parse(result.content[0].text);
// Assertions
expect(data).toHaveLength(1);
expect(data[0]).toHaveProperty('iataCode', 'JFK');
expect(data[0]).toHaveProperty('name', 'John F Kennedy International Airport');
});
});
```
--------------------------------------------------------------------------------
/src/queryAnalyzer.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
// Types of travel queries we can handle
export enum TravelQueryType {
INSPIRATION = 'inspiration', // "Where can I go..."
SPECIFIC_ROUTE = 'specific_route', // "Flights from X to Y"
MULTI_CITY = 'multi_city', // "Visit A, B, and C"
FLEXIBLE_VALUE = 'flexible_value', // "Cheapest time to fly..."
}
// Time expressions in queries
export enum TimeFrame {
SPECIFIC_DATE = 'specific_date', // "on June 15th"
DATE_RANGE = 'date_range', // "between June and July"
MONTH = 'month', // "in December"
SEASON = 'season', // "during summer"
HOLIDAY = 'holiday', // "for Christmas"
RELATIVE = 'relative', // "next month"
FLEXIBLE = 'flexible', // "whenever it's cheapest"
}
// Weather/Climate preferences
export enum ClimatePreference {
WARM = 'warm',
COLD = 'cold',
TROPICAL = 'tropical',
MILD = 'mild',
SUNNY = 'sunny',
ANY = 'any',
}
// Trip duration expressions
export interface Duration {
type: 'days' | 'weeks' | 'months' | 'flexible';
value: number | [number, number]; // Single value or range
isApproximate: boolean;
}
// Location reference in query
export interface LocationReference {
raw: string; // Original text
type: 'city' | 'airport' | 'region' | 'country';
code?: string; // Airport/city code if known
isFlexible: boolean; // Whether location is flexible ("somewhere warm")
context?: string; // Additional context ("beach cities", "major cities")
}
// Budget information
export interface BudgetConstraint {
amount: number;
currency: string;
type: 'total' | 'per_person' | 'per_flight';
isFlexible: boolean;
context?: string; // "cheap", "luxury", "best value"
}
// Travel preferences
export interface TravelPreferences {
purpose?: 'leisure' | 'business' | 'family' | 'adventure';
class?: 'economy' | 'premium_economy' | 'business' | 'first';
stops?: 'direct' | 'any' | number;
activities?: string[]; // "beach", "skiing", "sightseeing"
accommodation?: string[]; // "resort", "hotel", "any"
}
// The main structure for analyzed queries
export interface AnalyzedQuery {
type: TravelQueryType;
timeFrame: {
type: TimeFrame;
value: string | [string, string]; // Single date or range
isFlexible: boolean;
};
origin?: LocationReference;
destinations: LocationReference[];
duration?: Duration;
budget?: BudgetConstraint;
climate?: ClimatePreference;
preferences?: TravelPreferences;
rawQuery: string; // Original query text
confidence: number; // Confidence in the analysis (0-1)
ambiguities?: string[]; // List of unclear aspects
}
// Zod schema for validation
export const analyzedQuerySchema = z.object({
type: z.nativeEnum(TravelQueryType),
timeFrame: z.object({
type: z.nativeEnum(TimeFrame),
value: z.union([z.string(), z.tuple([z.string(), z.string()])]),
isFlexible: z.boolean(),
}),
origin: z.object({
raw: z.string(),
type: z.enum(['city', 'airport', 'region', 'country']),
code: z.string().optional(),
isFlexible: z.boolean(),
context: z.string().optional(),
}).optional(),
destinations: z.array(z.object({
raw: z.string(),
type: z.enum(['city', 'airport', 'region', 'country']),
code: z.string().optional(),
isFlexible: z.boolean(),
context: z.string().optional(),
})),
duration: z.object({
type: z.enum(['days', 'weeks', 'months', 'flexible']),
value: z.union([z.number(), z.tuple([z.number(), z.number()])]),
isApproximate: z.boolean(),
}).optional(),
budget: z.object({
amount: z.number(),
currency: z.string(),
type: z.enum(['total', 'per_person', 'per_flight']),
isFlexible: z.boolean(),
context: z.string().optional(),
}).optional(),
climate: z.nativeEnum(ClimatePreference).optional(),
preferences: z.object({
purpose: z.enum(['leisure', 'business', 'family', 'adventure']).optional(),
class: z.enum(['economy', 'premium_economy', 'business', 'first']).optional(),
stops: z.union([z.enum(['direct', 'any']), z.number()]).optional(),
activities: z.array(z.string()).optional(),
accommodation: z.array(z.string()).optional(),
}).optional(),
rawQuery: z.string(),
confidence: z.number().min(0).max(1),
ambiguities: z.array(z.string()).optional(),
});
// Helper function to identify query type
export function identifyQueryType(query: string): TravelQueryType {
const lowercaseQuery = query.toLowerCase();
// Inspiration patterns
if (lowercaseQuery.includes('where can i go') ||
lowercaseQuery.includes('suggest') ||
lowercaseQuery.includes('recommend')) {
return TravelQueryType.INSPIRATION;
}
// Multi-city patterns
if (lowercaseQuery.includes('visit') ||
lowercaseQuery.includes('multiple cities') ||
(lowercaseQuery.match(/,/g) || []).length >= 2) {
return TravelQueryType.MULTI_CITY;
}
// Flexible value patterns
if (lowercaseQuery.includes('cheapest time') ||
lowercaseQuery.includes('best time') ||
lowercaseQuery.includes('when should')) {
return TravelQueryType.FLEXIBLE_VALUE;
}
// Default to specific route
return TravelQueryType.SPECIFIC_ROUTE;
}
// Helper function to extract time frame
export function extractTimeFrame(query: string): {
type: TimeFrame;
value: string | [string, string];
isFlexible: boolean;
} {
// This is a placeholder - would need more sophisticated date parsing
return {
type: TimeFrame.FLEXIBLE,
value: '',
isFlexible: true,
};
}
// Helper function to extract locations
export function extractLocations(query: string): {
origin?: LocationReference;
destinations: LocationReference[];
} {
// This is a placeholder - would need location database and parsing
return {
destinations: [],
};
}
// Main analysis function
export async function analyzeQuery(query: string): Promise<AnalyzedQuery> {
const type = identifyQueryType(query);
const timeFrame = extractTimeFrame(query);
const locations = extractLocations(query);
const analysis: AnalyzedQuery = {
type,
timeFrame,
...locations,
rawQuery: query,
confidence: 0.8, // This should be calculated based on certainty of parsing
};
// Validate the analysis
analyzedQuerySchema.parse(analysis);
return analysis;
}
```
--------------------------------------------------------------------------------
/src/prompt.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
// Prompt for analyzing flight prices
import { server } from './index.js';
server.prompt(
'analyze-flight-prices',
'Analyze flight prices for a route',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
returnDate: z
.string()
.optional()
.describe('Return date in YYYY-MM-DD format (for round trips)'),
},
async ({
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
}) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please analyze flight prices for a trip from ${originLocationCode} to ${destinationLocationCode} departing on ${departureDate}${
returnDate ? ` and returning on ${returnDate}` : ''
}.
Please use the flight-price-analysis tool to get price metrics, and the search-flights tool to find actual flight options. Then provide:
1. An overview of the price range
2. Insights on whether current prices are high or low compared to average
3. Recommendations on when to book
4. A few specific flight options that offer good value
5. Any additional insights that would be helpful for a traveler
If you need to search for airport information, you can use the search-airports tool.`,
},
},
],
};
},
);
// Prompt for finding the best flight deals
server.prompt(
'find-best-deals',
'Find the best flight deals',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
returnDate: z
.string()
.optional()
.describe('Return date in YYYY-MM-DD format (for round trips)'),
travelClass: z
.enum(['ECONOMY', 'PREMIUM_ECONOMY', 'BUSINESS', 'FIRST'])
.optional()
.describe('Travel class'),
},
async ({
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
travelClass,
}) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please find the best flight deals for a trip from ${originLocationCode} to ${destinationLocationCode} departing on ${departureDate}${
returnDate ? ` and returning on ${returnDate}` : ''
}${travelClass ? ` in ${travelClass} class` : ''}.
Please use the search-flights tool to find options, and organize them by:
1. Best value options (considering price, duration, and convenience)
2. Cheapest options regardless of convenience
3. Most convenient options (fewest stops, best times)
For each option, provide a brief summary of why it might be a good choice for different types of travelers.`,
},
},
],
};
},
);
// Prompt for planning a multi-city trip
server.prompt(
'plan-multi-city-trip',
'Plan a multi-city trip',
{
cities: z
.string()
.describe('Comma-separated list of city or airport codes to visit'),
startDate: z.string().describe('Start date of trip in YYYY-MM-DD format'),
endDate: z.string().describe('End date of trip in YYYY-MM-DD format'),
homeAirport: z.string().length(3).describe('Home airport IATA code'),
},
async ({ cities, startDate, endDate, homeAirport }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
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}.
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.
For my trip plan, I would like:
1. The most logical order to visit these cities to minimize backtracking
2. Flight options between each city
3. Recommended number of days in each location based on the total trip duration
4. Any insights about potential challenges or considerations for this itinerary
Please outline a complete trip plan with flight details and suggested stays in each location.`,
},
},
],
};
},
);
// Prompt for finding cheapest dates to travel
server.prompt(
'find-cheapest-travel-dates',
'Find the cheapest dates to travel for a given route',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
earliestDepartureDate: z
.string()
.describe('Earliest possible departure date in YYYY-MM-DD format'),
latestDepartureDate: z
.string()
.describe('Latest possible departure date in YYYY-MM-DD format'),
tripDuration: z
.string()
.optional()
.describe('Desired trip duration in days (for round trips)'),
},
async ({
originLocationCode,
destinationLocationCode,
earliestDepartureDate,
latestDepartureDate,
tripDuration,
}) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I'm looking for the cheapest dates to fly from ${originLocationCode} to ${destinationLocationCode} between ${earliestDepartureDate} and ${latestDepartureDate}${
tripDuration
? ` for a trip duration of approximately ${tripDuration} days`
: ''
}.
Please use the find-cheapest-dates tool to identify the most economical travel dates, and then provide:
1. A list of the cheapest date combinations
2. An analysis of price trends during this period
3. Recommendations on the best days of the week to travel for this route
4. Any holidays or events that might be affecting pricing
5. Specific flight options for the cheapest dates found
Please organize this information clearly to help me make an informed decision about when to book my trip.`,
},
},
],
};
},
);
// Prompt for discovering flight destinations
server.prompt(
'discover-destinations',
'Find inspiring flight destinations within your budget',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., MAD)'),
maxPrice: z
.string()
.optional()
.describe('Maximum budget for flights'),
departureDate: z
.string()
.optional()
.describe('Preferred departure date or date range (YYYY-MM-DD)'),
tripDuration: z
.string()
.optional()
.describe('Desired trip duration in days (e.g., "7" or "2,8" for range)'),
},
async ({ originLocationCode, maxPrice, departureDate, tripDuration }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please help me discover interesting destinations I can fly to from ${originLocationCode}${
maxPrice ? ` within a budget of ${maxPrice}` : ''
}${departureDate ? ` around ${departureDate}` : ''}${
tripDuration ? ` for about ${tripDuration} days` : ''
}.
Please use the flight-inspiration tool to find destinations and then:
1. Group destinations by region or country
2. Highlight the best deals found
3. Provide insights about seasonal trends
4. Suggest specific destinations that offer good value
5. Include any interesting destinations that might be unexpected
For the most interesting options, please use the search-flights tool to find specific flight details.
If needed, use the search-airports tool to get more information about the destinations.
Please organize the results to help me discover new travel possibilities within my constraints.`,
},
},
],
};
},
);
// Prompt for exploring airport routes
server.prompt(
'explore-airport-routes',
'Discover direct routes and connections from an airport',
{
airportCode: z
.string()
.length(3)
.describe('Airport IATA code (e.g., JFK)'),
maxResults: z
.string()
.optional()
.default("20")
.describe('Maximum number of routes to show'),
},
async ({ airportCode, maxResults }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please analyze the routes available from ${airportCode} airport.
Please use the airport-routes tool to find direct destinations, and then:
1. Group destinations by region/continent
2. Highlight major routes with high flight frequency
3. Identify popular leisure and business destinations
4. List any seasonal or unique routes
5. Provide insights about the airport's connectivity
For key routes, please use the search-flights tool to check typical prices and schedules.
Use the search-airports tool to get more details about the connected airports.
Please organize this information to help understand:
- The airport's route network
- Best connection possibilities
- Popular destinations served
- Unique route opportunities`,
},
},
],
};
},
);
// Prompt for finding nearby airports
server.prompt(
'find-nearby-airports',
'Find convenient airports near a specific location',
{
latitude: z.string().describe('Location latitude'),
longitude: z.string().describe('Location longitude'),
radius: z
.string()
.optional()
.default("500")
.describe('Search radius in kilometers'),
maxResults: z
.string()
.optional()
.default("10")
.describe('Maximum number of airports to show'),
},
async ({ latitude, longitude, radius, maxResults }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please help me find convenient airports near latitude ${latitude}, longitude ${longitude}${
radius ? ` within ${radius} kilometers` : ''
}.
Please use the nearest-airports tool to find airports, and then:
1. Rank airports by convenience (considering distance and flight options)
2. Provide key information about each airport (size, typical destinations)
3. Compare transportation options to/from each airport
4. Highlight any airports with unique advantages
5. Suggest which airports might be best for different types of trips
For the most relevant airports:
- Use the airport-routes tool to check available destinations
- Use the search-flights tool to compare typical prices
- Consider factors like flight frequency and seasonal variations
Please organize this information to help choose the most suitable airport based on:
- Distance and accessibility
- Flight options and frequencies
- Typical prices
- Overall convenience for different types of travel`,
},
},
],
};
},
);
// Prompt for comprehensive trip planning
server.prompt(
'plan-complete-trip',
'Get comprehensive trip planning assistance',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code'),
budget: z.string().optional().describe('Total budget for flights'),
departureDate: z
.string()
.optional()
.describe('Preferred departure date or date range'),
tripDuration: z
.string()
.optional()
.describe('Desired trip duration in days'),
preferences: z
.string()
.optional()
.describe('Travel preferences (e.g., "beach, culture, food")'),
},
async ({
originLocationCode,
budget,
departureDate,
tripDuration,
preferences,
}) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please help me plan a trip from ${originLocationCode}${
budget ? ` with a budget of ${budget}` : ''
}${departureDate ? ` around ${departureDate}` : ''}${
tripDuration ? ` for ${tripDuration} days` : ''
}${preferences ? ` focusing on ${preferences}` : ''}.
Please use multiple tools to create a comprehensive trip plan:
1. Use the flight-inspiration tool to discover potential destinations that match my criteria
2. Use the nearest-airports tool to find alternative departure/arrival airports
3. Use the airport-routes tool to understand connection possibilities
4. Use the find-cheapest-dates tool to optimize travel dates
5. Use the search-flights tool to find specific flight options
Please provide:
1. Top destination recommendations based on my criteria
2. Best flight options and routing suggestions
3. Price analysis and booking timing recommendations
4. Alternative airports to consider
5. A complete trip outline with:
- Recommended destinations
- Flight options and prices
- Suggested itinerary
- Travel tips and considerations
Please organize all this information into a clear, actionable trip plan.`,
},
},
],
};
},
);
```
--------------------------------------------------------------------------------
/api-spectifications/AirportRoutes_v1_Version_1.1_swagger_specification.json:
--------------------------------------------------------------------------------
```json
{
"openapi": "3.0.0",
"info": {
"title": "Airport Routes",
"version": "1.1.0",
"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."
},
"servers": [
{
"url": "https://test.api.amadeus.com/v1"
}
],
"paths": {
"/airport/direct-destinations": {
"get": {
"summary": "get airport direct routes",
"tags": [
"direct-destinations"
],
"responses": {
"200": {
"description": "Success Response",
"content": {
"application/vnd.amadeus+json": {
"schema": {
"type": "object",
"properties": {
"warnings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/warnings"
}
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/locations"
}
},
"meta": {
"$ref": "#/components/schemas/meta"
}
}
},
"examples": {
"Successful Reply": {
"value": {
"meta": {
"count": 3,
"links": {
"self": "https://api.amadeus.com/v1/airport/direct-destinations?departureAirportCode=ORY&max=3&arrivalCountryCode=FR"
}
},
"data": [
{
"type": "location",
"subtype": "city",
"name": "TOULOUSE",
"iataCode": "TLS",
"geoCode": {
"latitude": 43.62908,
"longitude": 1.36382
},
"address": {
"cityName": "TOULOUSE",
"countryName": "FRANCE",
"stateCode": "FR-31",
"regionCode": "EUROP"
},
"timeZone": {
"offset": "+02:00",
"referenceLocalDateTime": "2022-10-25T10:17:00"
},
"metrics": {
"relevance": 100
}
},
{
"type": "location",
"subtype": "city",
"name": "NICE",
"iataCode": "NCE",
"geoCode": {
"latitude": 43.66272,
"longitude": 7.20787
},
"address": {
"cityName": "NICE",
"countryName": "FRANCE",
"stateCode": "FR-06",
"regionCode": "EUROP"
},
"timeZone": {
"offset": "+02:00",
"referenceLocalDateTime": "2022-10-25T10:17:00"
},
"metrics": {
"relevance": 97
}
},
{
"type": "location",
"subtype": "city",
"name": "MARSEILLE",
"iataCode": "MRS",
"geoCode": {
"latitude": 43.43556,
"longitude": 5.21361
},
"address": {
"cityName": "MARSEILLE",
"countryName": "FRANCE",
"stateCode": "FR-06",
"regionCode": "EUROP"
},
"timeZone": {
"offset": "+02:00",
"referenceLocalDateTime": "2022-10-25T10:17:00"
},
"metrics": {
"relevance": 45
}
}
]
}
}
}
}
}
},
"400": {
"description": "Bad Request\n\ncode | title \n------- | ------------------------------------- \n572 | INVALID OPTION \n32171 | MANDATORY DATA MISSING \n477 | INVALID FORMAT ",
"content": {
"application/vnd.amadeus+json": {
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/errors"
}
}
}
},
"examples": {
"example-error 400": {
"value": {
"errors": [
{
"status": 400,
"code": 32171,
"title": "MANDATORY DATA MISSING",
"detail": "Missing mandatory query parameter 'departureAirportCode"
}
]
}
}
}
}
}
},
"500": {
"description": "Internal Server Error\n\ncode | title \n------- | ------------------------------------- \n141 | SYSTEM ERROR HAS OCCURRED",
"content": {
"application/vnd.amadeus+json": {
"schema": {
"type": "object",
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/errors"
}
}
}
},
"examples": {
"example-error 500": {
"value": {
"errors": [
{
"status": 500,
"code": 141,
"title": "SYSTEM ERROR HAS OCCURRED"
}
]
}
}
}
}
}
}
},
"operationId": "airport/direct-destinations",
"parameters": [
{
"schema": {
"type": "string"
},
"in": "query",
"name": "departureAirportCode",
"description": "Departure Airport code following [IATA standard](http://www.iata.org/publications/Pages/code-search.aspx)",
"required": true,
"example": "BLR"
},
{
"schema": {
"type": "integer"
},
"in": "query",
"name": "max",
"description": "Maximum number of destination in the response."
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "arrivalCountryCode",
"description": "Arrival country code following [IATA standard](http://www.iata.org/publications/Pages/code-search.aspx), to filter the list of destinations"
}
],
"description": ""
}
}
},
"components": {
"schemas": {
"locations": {
"title": "destination",
"type": "object",
"description": "Description of a particular point or place in physical space",
"properties": {
"type": {
"type": "string",
"description": "type of API result \"location\""
},
"subtype": {
"type": "string",
"description": "Location sub-type (e.g. airport, port, rail-station, restaurant, atm...)"
},
"name": {
"type": "string",
"description": "Label associated to the location (e.g. Eiffel Tower, Madison Square)",
"example": "\"Eiffel Tower\""
},
"iataCode": {
"type": "string",
"description": "IATA location code",
"example": "\"PAR\""
},
"geoCode": {
"type": "object",
"description": "Geographic coordinates describing the position of any location on the surface of Earth",
"properties": {
"latitude": {
"type": "number",
"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.",
"example": "48.85837",
"minimum": -3.402823669209385e+38,
"multipleOf": 3.402823669209385e+38
},
"longitude": {
"type": "number",
"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.",
"example": "2.294481",
"minimum": -3.402823669209385e+38,
"multipleOf": 3.402823669209385e+38
}
}
},
"address": {
"type": "object",
"properties": {
"countryName": {
"type": "string",
"description": "Name of the country of the location",
"example": "France"
},
"countryCode": {
"type": "string",
"description": "Code of the country of the location in ISO standard",
"example": "FR"
},
"stateCode": {
"type": "string",
"description": "Code of the state of the location (if any)",
"example": "FR-13"
},
"regionCode": {
"type": "string",
"description": "Code of the region of the location in ISO standard",
"example": "EUROP"
}
}
},
"timeZone": {
"type": "object",
"properties": {
"offSet": {
"type": "string",
"description": "'Total offset from UTC including the Daylight Saving Time (DST) following ISO 8601 (https://en.wikipedia.org/wiki/ISO_8601) standard'",
"example": "+01:00"
},
"referenceLocalDateTime": {
"type": "string",
"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.",
"example": "2022-09-28T19:20:30"
}
}
},
"metrics": {
"type": "object",
"properties": {
"relevance": {
"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",
"example": "100",
"type": "integer"
}
}
}
}
},
"meta": {
"title": "meta",
"type": "object",
"description": "Meta information about the returned object(s) in \"data\"",
"properties": {
"count": {
"type": "integer",
"description": "Total number of object(s) retrieved",
"format": "int64"
},
"links": {
"type": "object",
"description": "Links related to the returned object(s)",
"properties": {
"self": {
"type": "string",
"description": "Link to the same page.",
"format": "uri"
}
}
}
}
},
"warnings": {
"title": "warning",
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": "A machine-readable error code from the Canned Messages table, that will enable the API Consumers code to handle this type of error"
},
"title": {
"type": "string",
"description": "An error title from the Canned Messages table with a 1:1 correspondence to the error code. This may be localized"
},
"detail": {
"type": "string",
"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."
},
"source": {
"type": "object",
"description": "The Warning Source Definition",
"properties": {
"parameter": {
"type": "string",
"description": "The key of the URI path or query parameter that caused the error"
},
"pointer": {
"type": "string",
"description": "A JSON Pointer RFC6901 to the associated entity in the request body that caused this error"
},
"example": {
"type": "string",
"description": "A sample input to guide the user when resolving this issue"
}
}
}
}
},
"errors": {
"title": "Error",
"properties": {
"status": {
"type": "integer",
"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."
},
"code": {
"type": "integer",
"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"
},
"title": {
"type": "string",
"description": "An error title from the Canned Messages table with a 1:1 correspondence to the error code. This may be localized"
},
"detail": {
"type": "string",
"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."
},
"source": {
"type": "object",
"title": "Error_Source",
"properties": {
"parameter": {
"type": "string",
"description": "The key of the URI path or query parameter that caused the error"
},
"pointer": {
"type": "string",
"description": "A JSON Pointer [RFC6901] to the associated entity in the request body that caused this error"
},
"example": {
"type": "string",
"description": "A sample input to guide the user when resolving this issue"
}
}
}
}
}
}
}
}
```
--------------------------------------------------------------------------------
/api-spectifications/AirportCitySearch_v1_Version_1.0_swagger_specification.json:
--------------------------------------------------------------------------------
```json
{
"swagger": "2.0",
"info": {
"version": "1.2.3",
"title": "Airport & City Search",
"x-status": "validated",
"x-tags": [
"#ama-for-dev"
],
"x-release-note": {
"1.2": [
"Remove parameter onlyMajor",
"Correct example"
],
"1.1": [
"AFD-1091 - change from \"traveller\" to \"traveler\"",
"change default value of view indicator to FULL",
"Change search algorithm",
"Addition of \"id\" for location",
"New operation GET Airport or City by id",
"Traveler score become interger (PTR 14827552)",
"Change the option parameter into view and onlyMajor parameter",
"add a characters restriction on keyword parameter"
],
"1.0": [
"Initial Version"
]
},
"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. "
},
"host": "test.api.amadeus.com",
"basePath": "/v1",
"schemes": [
"https"
],
"consumes": [
"application/vnd.amadeus+json"
],
"produces": [
"application/vnd.amadeus+json"
],
"paths": {
"/reference-data/locations": {
"get": {
"tags": [
"location"
],
"operationId": "getAirportCitySearch",
"summary": "Returns a list of airports and cities matching a given keyword.",
"parameters": [
{
"name": "subType",
"description": "sub type of the location (AIRPORT and/or CITY)",
"in": "query",
"required": true,
"type": "array",
"items": {
"type": "string",
"enum": [
"AIRPORT",
"CITY"
]
},
"collectionFormat": "csv",
"x-example": "CITY"
},
{
"name": "keyword",
"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./:-'()\"",
"in": "query",
"required": true,
"type": "string",
"x-example": "MUC",
"pattern": "[A-Za-z0-9./:()'\"-]"
},
{
"name": "countryCode",
"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).",
"in": "query",
"required": false,
"type": "string"
},
{
"$ref": "#/parameters/pageLimit"
},
{
"$ref": "#/parameters/pageOffset"
},
{
"$ref": "#/parameters/sort"
},
{
"$ref": "#/parameters/view"
}
],
"responses": {
"200": {
"$ref": "#/responses/airport-city-autocomplete"
},
"400": {
"$ref": "#/responses/400"
},
"default": {
"$ref": "#/responses/500"
}
},
"description": ""
}
},
"/reference-data/locations/{locationId}": {
"get": {
"tags": [
"location"
],
"operationId": "getAirportCity",
"summary": "Returns a specific airports or cities based on its id.",
"parameters": [
{
"$ref": "#/parameters/locationId"
}
],
"responses": {
"200": {
"$ref": "#/responses/airport-city"
},
"400": {
"$ref": "#/responses/400_GET-Id"
},
"404": {
"$ref": "#/responses/404"
},
"default": {
"$ref": "#/responses/500"
}
},
"description": ""
}
}
},
"parameters": {
"pageLimit": {
"name": "page[limit]",
"description": "maximum items in one page",
"required": false,
"in": "query",
"type": "integer",
"default": 10
},
"pageOffset": {
"name": "page[offset]",
"description": "start index of the requested page",
"required": false,
"in": "query",
"type": "integer",
"default": 0
},
"sort": {
"name": "sort",
"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",
"required": false,
"in": "query",
"type": "string",
"default": "analytics.travelers.score",
"enum": [
"analytics.travelers.score"
]
},
"view": {
"name": "view",
"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",
"required": false,
"in": "query",
"default": "FULL",
"type": "string",
"enum": [
"LIGHT",
"FULL"
]
},
"locationId": {
"name": "locationId",
"description": "identifier of the location",
"required": true,
"in": "path",
"type": "string",
"x-example": "CMUC"
}
},
"definitions": {
"Location": {
"properties": {
"id": {
"description": "id of the ressource",
"type": "string"
},
"self": {
"$ref": "#/definitions/Links"
},
"type": {
"description": "the resource name",
"type": "string",
"example": "location"
},
"subType": {
"description": "location sub type",
"type": "string",
"enum": [
"AIRPORT",
"CITY",
"POINT_OF_INTEREST",
"DISTRICT"
],
"example": "AIRPORT"
},
"name": {
"description": "short name of the location",
"type": "string",
"example": "Paris CDG"
},
"detailedName": {
"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",
"type": "string",
"example": "Paris/FR: Charles de Gaulle"
},
"timeZoneOffset": {
"description": "timezone offset of the location at the date of the API call (including daylight saving time)",
"type": "string",
"example": "+01:00"
},
"iataCode": {
"description": "IATA code of the location. ([IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) here)",
"type": "string",
"example": "CDG"
},
"geoCode": {
"$ref": "#/definitions/GeoCode"
},
"address": {
"$ref": "#/definitions/Address"
},
"distance": {
"$ref": "#/definitions/Distance"
},
"analytics": {
"$ref": "#/definitions/Analytics"
},
"relevance": {
"type": "number",
"format": "double",
"description": "score value calculated based on distance and analytics",
"example": 9.6584
},
"category": {
"description": "category of the location",
"type": "string",
"enum": [
"SIGHTS",
"BEACH_PARK",
"HISTORICAL",
"NIGHTLIFE",
"RESTAURANT",
"SHOPPING"
],
"example": "HISTORICAL"
},
"tags": {
"description": "list of tags related to the location",
"type": "array",
"items": {
"type": "string",
"example": [
"grocery",
"japanese",
"cafe"
]
}
},
"rank": {
"description": "the rank is the position compared to other locations based on how famous is a place. 1 being the highest.",
"type": "string",
"example": 1
}
}
},
"Address": {
"properties": {
"cityName": {
"description": "name of the city of the location; equal to name if the location is a city",
"type": "string",
"example": "Paris"
},
"cityCode": {
"description": "IATA code of the city of the location; equal to IATAcode if the location is a city",
"type": "string",
"example": "PAR"
},
"countryName": {
"description": "name of the country of the location",
"type": "string",
"example": "France"
},
"countryCode": {
"description": "code of the country of the location in ISO standard",
"type": "string",
"example": "FR"
},
"stateCode": {
"description": "code of the state of the location if any",
"type": "string",
"example": "TO"
},
"regionCode": {
"description": "code of the region of the location in ISO standard",
"type": "string",
"example": "EUROP"
}
}
},
"Distance": {
"properties": {
"value": {
"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.",
"type": "integer",
"example": 152
},
"unit": {
"description": "unit of the distance",
"type": "string",
"example": "KM",
"enum": [
"KM",
"MI"
]
}
}
},
"GeoCode": {
"properties": {
"latitude": {
"description": "latitude of the location",
"type": "number",
"format": "double",
"example": 43.580418
},
"longitude": {
"description": "longitude of the location",
"type": "number",
"format": "double",
"example": 7.125102
}
}
},
"Analytics": {
"properties": {
"travelers": {
"$ref": "#/definitions/Travelers"
}
}
},
"Travelers": {
"properties": {
"score": {
"type": "number",
"format": "integer",
"description": "Approximate score for ranking purposes calculated based on number of travelers in the location.",
"example": 68
}
}
},
"Error_400": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 400,
"code": 477,
"title": "INVALID FORMAT",
"detail": "invalid query parameter format",
"source": {
"parameter": "airport",
"example": "CDG"
}
}
]
}
},
"Error_404": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 404,
"code": 1797,
"title": "NOT FOUND",
"detail": "no response found for this query parameter",
"source": {
"parameter": "airport"
}
}
]
}
},
"Error_500": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 500,
"code": 141,
"title": "SYSTEM ERROR HAS OCCURRED"
}
]
}
},
"Issue": {
"properties": {
"status": {
"description": "the HTTP status code applicable to this error",
"type": "integer"
},
"code": {
"description": "an application-specific error code",
"type": "integer",
"format": "int64"
},
"title": {
"description": "a short summary of the error",
"type": "string"
},
"detail": {
"description": "explanation of the error",
"type": "string"
},
"source": {
"type": "object",
"title": "Issue_Source",
"description": "an object containing references to the source of the error",
"maxProperties": 1,
"properties": {
"pointer": {
"description": "a JSON Pointer [RFC6901] to the associated entity in the request document",
"type": "string"
},
"parameter": {
"description": "a string indicating which URI query parameter caused the issue",
"type": "string"
},
"example": {
"description": "a string indicating an example of the right value",
"type": "string"
}
}
}
}
},
"Collection_Meta": {
"title": "Collection_Meta",
"properties": {
"count": {
"type": "integer",
"example": 1
},
"links": {
"title": "CollectionLinks",
"properties": {
"self": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"next": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"previous": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"last": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"first": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"up": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
}
},
"example": {
"self": "https://test.api.amadeus.com/v1/area/resources?param=value"
}
}
}
},
"Links": {
"required": [
"href"
],
"properties": {
"href": {
"type": "string",
"format": "uri"
},
"methods": {
"type": "array",
"items": {
"type": "string",
"enum": [
"GET",
"PUT",
"DELETE",
"POST",
"PATCH"
]
}
},
"count": {
"type": "integer"
}
},
"example": {
"href": "string"
}
}
},
"responses": {
"400": {
"description": "code | title \n------- | ------------------------------------- \n477 | INVALID FORMAT\n572 | INVALID OPTION \n2781 | INVALID LENGTH\n4926 | INVALID DATA RECEIVED \n32171 | MANDATORY DATA MISSING \t \n",
"schema": {
"$ref": "#/definitions/Error_400"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/Error_404"
}
},
"500": {
"description": "Unexpected Error",
"schema": {
"$ref": "#/definitions/Error_500"
}
},
"airport-city-autocomplete": {
"description": "Successful Operation",
"schema": {
"title": "Success",
"required": [
"data"
],
"properties": {
"meta": {
"$ref": "#/definitions/Collection_Meta"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Location"
}
}
},
"example": {
"meta": {
"count": 2,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=CITY,AIRPORT&keyword=MUC&countryCode=DE"
}
},
"data": [
{
"type": "location",
"subType": "CITY",
"name": "MUNICH INTERNATIONAL",
"detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
"id": "CMUC",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC",
"methods": [
"GET"
]
},
"timeZoneOffset": "+02:00",
"iataCode": "MUC",
"geoCode": {
"latitude": 48.35378,
"longitude": 11.78609
},
"address": {
"cityName": "MUNICH",
"cityCode": "MUC",
"countryName": "GERMANY",
"countryCode": "DE",
"regionCode": "EUROP"
},
"analytics": {
"travelers": {
"score": 27
}
}
},
{
"type": "location",
"subType": "AIRPORT",
"name": "MUNICH INTERNATIONAL",
"detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
"id": "AMUC",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/AMUC",
"methods": [
"GET"
]
},
"timeZoneOffset": "+02:00",
"iataCode": "MUC",
"geoCode": {
"latitude": 48.35378,
"longitude": 11.78609
},
"address": {
"cityName": "MUNICH",
"cityCode": "MUC",
"countryName": "GERMANY",
"countryCode": "DE",
"regionCode": "EUROP"
},
"analytics": {
"travelers": {
"score": 27
}
}
}
]
}
}
},
"airport-city": {
"description": "Successful Operation",
"schema": {
"title": "Success",
"required": [
"data"
],
"properties": {
"meta": {
"$ref": "#/definitions/Collection_Meta"
},
"data": {
"$ref": "#/definitions/Location"
}
},
"example": {
"meta": {
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC"
}
},
"data": {
"type": "location",
"subType": "CITY",
"name": "MUNICH INTERNATIONAL",
"detailedName": "MUNICH/DE:MUNICH INTERNATIONAL",
"id": "CMUC",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/CMUC",
"methods": [
"GET"
]
},
"timeZoneOffset": "+02:00",
"iataCode": "MUC",
"geoCode": {
"latitude": 48.35378,
"longitude": 11.78609
},
"address": {
"cityName": "MUNICH",
"cityCode": "MUC",
"countryName": "GERMANY",
"countryCode": "DE",
"regionCode": "EUROP"
},
"analytics": {
"travelers": {
"score": 27
}
}
}
}
}
},
"400_GET-Id": {
"description": "code | title \n------- | ------------------------------------- \n572 | INVALID OPTION \n",
"schema": {
"$ref": "#/definitions/Error_400"
}
}
},
"x-generatedAt": "2020-07-24T09:50:53.074Z"
}
```
--------------------------------------------------------------------------------
/api-spectifications/AirportNearestRelevant_v1_Version_1.0_swagger_specification.json:
--------------------------------------------------------------------------------
```json
{
"swagger": "2.0",
"info": {
"version": "1.1.2",
"title": "Airport Nearest Relevant",
"x-tags": [
"#ama-for-dev"
],
"x-status": "validated",
"x-release-note": {
"1.1.1": [
"Correct example"
],
"1.1.0": [
"Add radius parameter"
],
"1.0.2": [
"AFD-1091 - change from \"traveller\" to \"traveler\""
],
"1.0.1": [
"Improvement of relevance calculation",
"Remove flights and travelers figures",
"Flights and Travelers score become integer (PTR 14827552)"
],
"1.0": [
"Initial Version"
]
},
"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)**."
},
"host": "test.api.amadeus.com",
"basePath": "/v1",
"schemes": [
"https"
],
"consumes": [
"application/vnd.amadeus+json"
],
"produces": [
"application/vnd.amadeus+json"
],
"paths": {
"/reference-data/locations/airports": {
"get": {
"tags": [
"location"
],
"operationId": "getNearestRelevantAirports",
"summary": "Returns a list of relevant airports near to a given point.",
"parameters": [
{
"name": "latitude",
"description": "latitude location to be at the center of the search circle",
"in": "query",
"required": true,
"type": "number",
"format": "double",
"x-example": 51.57285
},
{
"name": "longitude",
"description": "longitude location to be at the center of the search circle",
"in": "query",
"required": true,
"type": "number",
"format": "double",
"x-example": -0.44161
},
{
"name": "radius",
"description": "radius of the search in Kilometer. Can be from 0 to 500, default value is 500 Km.",
"in": "query",
"required": false,
"type": "integer",
"minimum": 0,
"maximum": 500,
"default": 500
},
{
"$ref": "#/parameters/pageLimit"
},
{
"$ref": "#/parameters/pageOffset"
},
{
"$ref": "#/parameters/sort"
}
],
"responses": {
"200": {
"$ref": "#/responses/nearest-relevant-airports"
},
"400": {
"$ref": "#/responses/400"
},
"default": {
"$ref": "#/responses/500"
}
},
"description": ""
}
}
},
"parameters": {
"pageLimit": {
"name": "page[limit]",
"description": "maximum items in one page",
"required": false,
"in": "query",
"type": "integer",
"default": 10
},
"pageOffset": {
"name": "page[offset]",
"description": "start index of the requested page",
"required": false,
"in": "query",
"type": "integer",
"default": 0
},
"sort": {
"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",
"name": "sort",
"required": false,
"in": "query",
"type": "string",
"default": "relevance",
"enum": [
"relevance",
"distance",
"analytics.flights.score",
"analytics.travelers.score"
]
}
},
"definitions": {
"Location": {
"properties": {
"type": {
"description": "the resource name",
"type": "string",
"example": "location"
},
"subType": {
"description": "location sub type",
"type": "string",
"enum": [
"AIRPORT",
"CITY",
"POINT_OF_INTEREST",
"DISTRICT"
],
"example": "AIRPORT"
},
"name": {
"description": "short name of the location",
"type": "string",
"example": "Paris CDG"
},
"detailedName": {
"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",
"type": "string",
"example": "Paris/FR: Charles de Gaulle"
},
"timeZoneOffset": {
"description": "timezone offset of the location at the date of the API call (including daylight saving time)",
"type": "string",
"example": "+01:00"
},
"iataCode": {
"description": "IATA code of the location. ([IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) here)",
"type": "string",
"example": "CDG"
},
"geoCode": {
"$ref": "#/definitions/GeoCode"
},
"address": {
"$ref": "#/definitions/Address"
},
"distance": {
"$ref": "#/definitions/Distance"
},
"analytics": {
"$ref": "#/definitions/Analytics"
},
"relevance": {
"type": "number",
"format": "double",
"description": "score value calculated based on distance and analytics",
"example": 9.6584
}
}
},
"Address": {
"properties": {
"cityName": {
"description": "name of the city of the location; equal to name if the location is a city",
"type": "string",
"example": "Paris"
},
"cityCode": {
"description": "IATA code of the city of the location; equal to IATAcode if the location is a city",
"type": "string",
"example": "PAR"
},
"countryName": {
"description": "name of the country of the location",
"type": "string",
"example": "France"
},
"countryCode": {
"description": "code of the country of the location in ISO standard",
"type": "string",
"example": "FR"
},
"stateCode": {
"description": "code of the state of the location if any",
"type": "string",
"example": "TO"
},
"regionCode": {
"description": "code of the region of the location in ISO standard",
"type": "string",
"example": "EUROP"
}
}
},
"Distance": {
"properties": {
"value": {
"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.",
"type": "integer",
"example": 152
},
"unit": {
"description": "unit of the distance",
"type": "string",
"example": "KM",
"enum": [
"KM",
"MI"
]
}
}
},
"GeoCode": {
"properties": {
"latitude": {
"description": "latitude of the location",
"type": "number",
"format": "double",
"example": 43.580418
},
"longitude": {
"description": "longitude of the location",
"type": "number",
"format": "double",
"example": 7.125102
}
}
},
"Analytics": {
"properties": {
"flights": {
"$ref": "#/definitions/Flights"
},
"travelers": {
"$ref": "#/definitions/Travelers"
}
}
},
"Flights": {
"properties": {
"score": {
"type": "number",
"format": "integer",
"description": "Approximate score for ranking purposes calculated based on number of flights from / to the airport or city",
"example": 56
}
}
},
"Travelers": {
"properties": {
"score": {
"type": "number",
"format": "integer",
"description": "Approximate score for ranking purposes calculated based on number of travelers in the location.",
"example": 68
}
}
},
"Error_400": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 400,
"code": 477,
"title": "INVALID FORMAT",
"detail": "invalid query parameter format",
"source": {
"parameter": "airport",
"example": "CDG"
}
}
]
}
},
"Error_500": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 500,
"code": 141,
"title": "SYSTEM ERROR HAS OCCURRED"
}
]
}
},
"Issue": {
"properties": {
"status": {
"description": "the HTTP status code applicable to this error",
"type": "integer"
},
"code": {
"description": "an application-specific error code",
"type": "integer",
"format": "int64"
},
"title": {
"description": "a short summary of the error",
"type": "string"
},
"detail": {
"description": "explanation of the error",
"type": "string"
},
"source": {
"type": "object",
"title": "Issue_Source",
"description": "an object containing references to the source of the error",
"maxProperties": 1,
"properties": {
"pointer": {
"description": "a JSON Pointer [RFC6901] to the associated entity in the request document",
"type": "string"
},
"parameter": {
"description": "a string indicating which URI query parameter caused the issue",
"type": "string"
},
"example": {
"description": "a string indicating an example of the right value",
"type": "string"
}
}
}
}
},
"Collection_Meta": {
"title": "Collection_Meta",
"properties": {
"count": {
"type": "integer",
"example": 1
},
"links": {
"title": "CollectionLinks",
"properties": {
"self": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"next": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"previous": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"last": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"first": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
},
"up": {
"type": "string",
"format": "uri",
"example": "https://test.api.amadeus.com/v1/area/resources?..."
}
},
"example": {
"self": "https://test.api.amadeus.com/v1/area/resources?param=value"
}
}
}
}
},
"responses": {
"400": {
"description": "code | title \n------- | ------------------------------------- \n477 | INVALID FORMAT\n572 | INVALID OPTION\n4926 | INVALID DATA RECEIVED \n32171 | MANDATORY DATA MISSING \t \n",
"schema": {
"$ref": "#/definitions/Error_400"
}
},
"500": {
"description": "Unexpected Error",
"schema": {
"$ref": "#/definitions/Error_500"
}
},
"nearest-relevant-airports": {
"description": "Successful Operation",
"schema": {
"title": "Success",
"required": [
"data"
],
"properties": {
"meta": {
"$ref": "#/definitions/Collection_Meta"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Location"
}
}
},
"example": {
"meta": {
"count": 32,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161",
"next": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161&page%5Boffset%5D=10",
"last": "https://test.api.amadeus.com/v1/reference-data/locations/airports?latitude=51.57285&longitude=-0.44161&page%5Boffset%5D=22"
}
},
"data": [
{
"type": "location",
"subType": "AIRPORT",
"name": "HEATHROW",
"detailedName": "LONDON/GB:HEATHROW",
"timeZoneOffset": "+01:00",
"iataCode": "LHR",
"geoCode": {
"latitude": 51.47294,
"longitude": -0.45061
},
"address": {
"cityName": "LONDON",
"cityCode": "LON",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 11,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 39
},
"travelers": {
"score": 45
}
},
"relevance": 350.54587
},
{
"type": "location",
"subType": "AIRPORT",
"name": "GATWICK",
"detailedName": "LONDON/GB:GATWICK",
"timeZoneOffset": "+01:00",
"iataCode": "LGW",
"geoCode": {
"latitude": 51.15609,
"longitude": -0.17818
},
"address": {
"cityName": "LONDON",
"cityCode": "LON",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 49,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 27
},
"travelers": {
"score": 27
}
},
"relevance": 53.62667
},
{
"type": "location",
"subType": "AIRPORT",
"name": "LUTON",
"detailedName": "LONDON/GB:LUTON",
"timeZoneOffset": "+01:00",
"iataCode": "LTN",
"geoCode": {
"latitude": 51.87472,
"longitude": -0.36833
},
"address": {
"cityName": "LONDON",
"cityCode": "LON",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 33,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 11
},
"travelers": {
"score": 10
}
},
"relevance": 33.10184
},
{
"type": "location",
"subType": "AIRPORT",
"name": "STANSTED",
"detailedName": "LONDON/GB:STANSTED",
"timeZoneOffset": "+01:00",
"iataCode": "STN",
"geoCode": {
"latitude": 51.885,
"longitude": 0.235
},
"address": {
"cityName": "LONDON",
"cityCode": "LON",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 58,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 16
},
"travelers": {
"score": 15
}
},
"relevance": 27.50241
},
{
"type": "location",
"subType": "AIRPORT",
"name": "CITY AIRPORT",
"detailedName": "LONDON/GB:CITY AIRPORT",
"timeZoneOffset": "+01:00",
"iataCode": "LCY",
"geoCode": {
"latitude": 51.50528,
"longitude": 0.05528
},
"address": {
"cityName": "LONDON",
"cityCode": "LON",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 35,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 8
},
"travelers": {
"score": 4
}
},
"relevance": 21.78754
},
{
"type": "location",
"subType": "AIRPORT",
"name": "BIRMINGHAM",
"detailedName": "BIRMINGHAM/GB:BIRMINGHAM",
"timeZoneOffset": "+01:00",
"iataCode": "BHX",
"geoCode": {
"latitude": 52.45386,
"longitude": -1.74803
},
"address": {
"cityName": "BIRMINGHAM",
"cityCode": "BHX",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 132,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 10
},
"travelers": {
"score": 8
}
},
"relevance": 7.73356
},
{
"type": "location",
"subType": "AIRPORT",
"name": "MANCHESTER AIRPORT",
"detailedName": "MANCHESTER/GB:MANCHESTER AIRPO",
"timeZoneOffset": "+01:00",
"iataCode": "MAN",
"geoCode": {
"latitude": 53.35374,
"longitude": -2.27495
},
"address": {
"cityName": "MANCHESTER",
"cityCode": "MAN",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 233,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 18
},
"travelers": {
"score": 17
}
},
"relevance": 7.71084
},
{
"type": "location",
"subType": "AIRPORT",
"name": "SOUTHAMPTON",
"detailedName": "SOUTHAMPTON/GB",
"timeZoneOffset": "+01:00",
"iataCode": "SOU",
"geoCode": {
"latitude": 50.95026,
"longitude": -1.3568
},
"address": {
"cityName": "SOUTHAMPTON",
"cityCode": "SOU",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 94,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 4
},
"travelers": {
"score": 2
}
},
"relevance": 4.4788
},
{
"type": "location",
"subType": "AIRPORT",
"name": "BRISTOL",
"detailedName": "BRISTOL/GB:BRISTOL",
"timeZoneOffset": "+01:00",
"iataCode": "BRS",
"geoCode": {
"latitude": 51.38267,
"longitude": -2.71909
},
"address": {
"cityName": "BRISTOL",
"cityCode": "BRS",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 159,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 7
},
"travelers": {
"score": 5
}
},
"relevance": 4.08617
},
{
"type": "location",
"subType": "AIRPORT",
"name": "EAST MIDLANDS",
"detailedName": "NOTTINGHAM/GB:EAST MIDLANDS",
"timeZoneOffset": "+01:00",
"iataCode": "EMA",
"geoCode": {
"latitude": 52.83111,
"longitude": -1.32806
},
"address": {
"cityName": "NOTTINGHAM",
"cityCode": "NQT",
"countryName": "UNITED KINGDOM",
"countryCode": "GB",
"regionCode": "EUROP"
},
"distance": {
"value": 152,
"unit": "KM"
},
"analytics": {
"flights": {
"score": 4
},
"travelers": {
"score": 3
}
},
"relevance": 2.66099
}
]
}
}
}
},
"x-generatedAt": "2020-07-22T14:53:48.686Z"
}
```
--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
// Tool to search for flights
import { amadeus, cachedApiCall, server } from './index.js';
// Define interfaces for Amadeus API responses and parameters
interface FlightParams {
[key: string]: string | number | boolean | undefined;
originLocationCode: string;
destinationLocationCode: string;
departureDate: string;
returnDate?: string;
adults: number;
children: number;
infants: number;
travelClass?: 'ECONOMY' | 'PREMIUM_ECONOMY' | 'BUSINESS' | 'FIRST';
nonStop: boolean;
currencyCode: string;
max: number;
}
interface AirportParams {
[key: string]: string | number | undefined;
keyword: string;
subType?: 'AIRPORT' | 'CITY';
countryCode?: string;
max: number;
}
interface PriceAnalysisParams {
[key: string]: string | undefined;
originLocationCode: string;
destinationLocationCode: string;
departureDate: string;
returnDate?: string;
currencyCode: string;
}
interface CheapestDateParams {
[key: string]: string | number | boolean | undefined;
originLocationCode: string;
destinationLocationCode: string;
departureDate: string;
returnDate?: string;
oneWay: boolean;
duration?: number;
nonStop: boolean;
viewBy: string;
currencyCode: string;
}
// Define interfaces for Amadeus API response objects
interface FlightSegment {
departure: {
iataCode: string;
at: string;
};
arrival: {
iataCode: string;
at: string;
};
carrierCode: string;
number: string;
}
interface FlightItinerary {
duration: string;
segments: FlightSegment[];
}
interface FlightOffer {
price: {
total: string;
currency: string;
};
itineraries: FlightItinerary[];
validatingAirlineCodes: string[];
numberOfBookableSeats?: number;
}
// Define response interfaces for API calls
interface FlightOfferResponse {
data: FlightOffer[];
}
interface AirportResponse {
data: Array<{
iataCode?: string;
name?: string;
cityCode?: string;
cityName?: string;
countryCode?: string;
countryName?: string;
[key: string]: string | number | boolean | undefined | null;
}>;
}
interface PriceAnalysisResponse {
data: Array<{
type: string;
origin: string;
destination: string;
departureDate: string;
returnDate?: string;
priceMetrics: Array<{
amount: string;
quartileRanking: string;
[key: string]: string | number | boolean | undefined | null;
}>;
[key: string]:
| string
| number
| boolean
| undefined
| null
| Array<Record<string, unknown>>;
}>;
}
server.tool(
'search-flights',
'Search for flight offers',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
returnDate: z
.string()
.optional()
.describe('Return date in YYYY-MM-DD format (for round trips)'),
adults: z.number().min(1).max(9).default(1).describe('Number of adults'),
children: z.number().min(0).default(0).describe('Number of children'),
infants: z.number().min(0).default(0).describe('Number of infants'),
travelClass: z
.enum(['ECONOMY', 'PREMIUM_ECONOMY', 'BUSINESS', 'FIRST'])
.optional()
.describe('Travel class'),
nonStop: z
.boolean()
.default(false)
.describe('Filter for non-stop flights only'),
currencyCode: z
.string()
.length(3)
.default('USD')
.describe('Currency code for pricing'),
maxResults: z
.number()
.min(1)
.max(250)
.default(20)
.describe('Maximum number of results'),
},
async ({
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
adults,
children,
infants,
travelClass,
nonStop,
currencyCode,
maxResults,
}) => {
try {
const params: FlightParams = {
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
adults,
children,
infants,
travelClass,
nonStop,
currencyCode,
max: maxResults,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
const response = (await amadeus.shopping.flightOffersSearch.get(
params,
)) as FlightOfferResponse;
const formattedResults = response.data.map((offer: FlightOffer) => {
const {
price,
itineraries,
validatingAirlineCodes,
numberOfBookableSeats,
} = offer;
// Format itineraries with more details
const formattedItineraries = itineraries.map(
(itinerary: FlightItinerary, idx: number) => {
// Calculate total duration in minutes
const totalDurationMinutes = Number.parseInt(
itinerary.duration.slice(2, -1),
);
// Format as hours and minutes
const hours = Math.floor(totalDurationMinutes / 60);
const minutes = totalDurationMinutes % 60;
const formattedDuration = `${hours}h ${minutes}m`;
// Count stops
const numStops = itinerary.segments.length - 1;
const stopsText =
numStops === 0
? 'Non-stop'
: `${numStops} stop${numStops > 1 ? 's' : ''}`;
// Format segments with times
const segments = itinerary.segments
.map((segment: FlightSegment) => {
const departureTime = new Date(
segment.departure.at,
).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
const arrivalTime = new Date(
segment.arrival.at,
).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
return `${segment.departure.iataCode} (${departureTime}) → ${segment.arrival.iataCode} (${arrivalTime}) - ${segment.carrierCode}${segment.number}`;
})
.join(' | ');
return {
type: idx === 0 ? 'Outbound' : 'Return',
duration: formattedDuration,
stops: stopsText,
segments,
};
},
);
return {
price: `${price.total} ${price.currency}`,
bookableSeats: numberOfBookableSeats || 'Unknown',
airlines: validatingAirlineCodes.join(', '),
itineraries: formattedItineraries,
};
});
return {
content: [
{
type: 'text',
text: JSON.stringify(formattedResults, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error searching flights:', error);
return {
content: [
{
type: 'text',
text: `Error searching flights: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
// Tool to search for airports
server.tool(
'search-airports',
'Search for airports by keyword',
{
keyword: z
.string()
.min(2)
.describe('Keyword to search for (city, airport name, IATA code)'),
subType: z
.enum(['AIRPORT', 'CITY'])
.optional()
.describe('Subtype to filter results'),
countryCode: z
.string()
.length(2)
.optional()
.describe('Two-letter country code to filter results'),
maxResults: z
.number()
.min(1)
.max(100)
.default(10)
.describe('Maximum number of results'),
},
async ({ keyword, subType, countryCode, maxResults }) => {
try {
const params: AirportParams = {
keyword,
subType,
countryCode,
max: maxResults,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
// Create a cache key based on the parameters
const cacheKey = `airport_search_${keyword}_${subType || ''}_${
countryCode || ''
}_${maxResults}`;
// Use the cached API call with a TTL of 24 hours (86400 seconds) since airport data rarely changes
const response = await cachedApiCall<AirportResponse>(
cacheKey,
86400,
() =>
amadeus.referenceData.locations.get(
params,
) as Promise<AirportResponse>,
);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error searching airports:', error);
return {
content: [
{
type: 'text',
text: `Error searching airports: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/*
* Tool to get flight price analysis
*/
server.tool(
'flight-price-analysis',
'Get flight price analysis for a route',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
returnDate: z
.string()
.optional()
.describe('Return date in YYYY-MM-DD format (for round trips)'),
currencyCode: z
.string()
.length(3)
.default('USD')
.describe('Currency code for pricing'),
},
async ({
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
currencyCode,
}) => {
try {
const params: PriceAnalysisParams = {
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
currencyCode,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
const response = (await amadeus.analytics.itineraryPriceMetrics.get(
params,
)) as PriceAnalysisResponse;
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error getting price analysis:', error);
return {
content: [
{
type: 'text',
text: `Error getting price analysis: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/**
* Tool to get detailed flight offer information
*/
server.tool(
'get-flight-details',
'Get detailed information about a specific flight offer',
{
flightOfferId: z.string().describe('The ID of the flight offer'),
},
async ({ flightOfferId }) => {
try {
// Flight offers need to be first retrieved then accessed by ID
// This is a simulated implementation as direct ID access isn't available in the basic API
// In a real implementation, you would either:
// 1. Cache flight offers and look them up by ID
// 2. Pass the entire flight offer object as a JSON string and parse it here
return {
content: [
{
type: 'text',
text: `To implement this properly for flight ID ${flightOfferId}, you would need to either:
1. Cache flight search results server-side and retrieve by ID
2. Pass the entire flight offer object as a JSON string parameter
3. Use Amadeus Flight Offers Price API for the most current and detailed information
Please modify this tool based on your specific implementation needs.`,
},
],
};
} catch (error: unknown) {
console.error('Error getting flight details:', error);
return {
content: [
{
type: 'text',
text: `Error getting flight details: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/**
* Tool to find cheapest travel dates
*/
interface CheapestDateResult {
data: Array<{
type: string;
origin: string;
destination: string;
departureDate: string;
returnDate?: string | null;
price: {
total: string;
currency: string;
};
}>;
}
server.tool(
'find-cheapest-dates',
'Find the cheapest dates to fly for a given route',
{
originLocationCode: z
.string()
.length(3)
.describe('Origin airport IATA code (e.g., JFK)'),
destinationLocationCode: z
.string()
.length(3)
.describe('Destination airport IATA code (e.g., LHR)'),
departureDate: z
.string()
.describe('Earliest departure date in YYYY-MM-DD format'),
returnDate: z
.string()
.optional()
.describe('Latest return date in YYYY-MM-DD format (for round trips)'),
duration: z
.number()
.optional()
.describe('Desired length of stay in days (for round trips)'),
currencyCode: z
.string()
.length(3)
.default('USD')
.describe('Currency code for pricing'),
},
async ({
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
duration,
currencyCode,
}) => {
try {
// Check if we have required parameters
if (!departureDate) {
return {
content: [
{
type: 'text',
text: 'Departure date is required for date search',
},
],
isError: true,
};
}
const params: CheapestDateParams = {
originLocationCode,
destinationLocationCode,
departureDate,
returnDate,
oneWay: !returnDate && !duration,
duration,
nonStop: false,
viewBy: 'DATE',
currencyCode,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
// Note: This endpoint requires Flight Offers Search API
// This is a placeholder for the actual implementation
// Normally, you'd use amadeus.shopping.flightDates.get(params)
// Simulate a response for demonstration
const simulatedResponse: CheapestDateResult = {
data: [
{
type: 'flight-date',
origin: originLocationCode,
destination: destinationLocationCode,
departureDate: departureDate,
returnDate: returnDate,
price: { total: '450.00', currency: currencyCode },
},
{
type: 'flight-date',
origin: originLocationCode,
destination: destinationLocationCode,
departureDate: new Date(
new Date(departureDate).getTime() + 86400000 * 2,
)
.toISOString()
.split('T')[0],
returnDate: returnDate
? new Date(new Date(returnDate).getTime() + 86400000 * 2)
.toISOString()
.split('T')[0]
: null,
price: { total: '425.00', currency: currencyCode },
},
],
};
return {
content: [
{
type: 'text',
text: `Note: This is currently a simulated response. To implement fully, you'll need to use the Flight Offers Search API with appropriate date ranges.\n\n${JSON.stringify(
simulatedResponse.data,
null,
2,
)}`,
},
],
};
} catch (error: unknown) {
console.error('Error finding cheapest dates:', error);
return {
content: [
{
type: 'text',
text: `Error finding cheapest dates: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/**
* Tool to search for flight inspiration destinations
*/
interface FlightInspirationParams {
[key: string]: string | number | boolean | undefined;
origin: string;
departureDate?: string;
oneWay?: boolean;
duration?: string;
nonStop?: boolean;
maxPrice?: number;
viewBy?: 'COUNTRY' | 'DATE' | 'DESTINATION' | 'DURATION' | 'WEEK';
}
interface FlightDestination {
type: string;
origin: string;
destination: string;
departureDate: string;
returnDate?: string;
price: {
total: string;
};
links: {
flightDates: string;
flightOffers: string;
};
}
interface FlightInspirationResponse {
data: FlightDestination[];
}
server.tool(
'flight-inspiration',
'Find the cheapest destinations where you can fly to',
{
origin: z
.string()
.length(3)
.describe('Origin airport/city IATA code (e.g., MAD)'),
departureDate: z
.string()
.optional()
.describe('Departure date or range (YYYY-MM-DD or YYYY-MM-DD,YYYY-MM-DD)'),
oneWay: z
.boolean()
.optional()
.default(false)
.describe('Whether to search for one-way flights only'),
duration: z
.string()
.optional()
.describe('Duration of stay in days (e.g., "7" or "2,8" for range)'),
nonStop: z
.boolean()
.optional()
.default(false)
.describe('Whether to search for non-stop flights only'),
maxPrice: z
.number()
.optional()
.describe('Maximum price limit'),
viewBy: z
.enum(['COUNTRY', 'DATE', 'DESTINATION', 'DURATION', 'WEEK'])
.optional()
.describe('How to group the results'),
},
async ({
origin,
departureDate,
oneWay,
duration,
nonStop,
maxPrice,
viewBy,
}) => {
try {
const params: FlightInspirationParams = {
origin,
departureDate,
oneWay,
duration,
nonStop,
maxPrice,
viewBy,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
const response = (await amadeus.shopping.flightDestinations.get(
params,
)) as FlightInspirationResponse;
// Format the response for better readability
const formattedResults = response.data.map((destination) => ({
destination: destination.destination,
departureDate: destination.departureDate,
returnDate: destination.returnDate,
price: destination.price.total,
links: destination.links,
}));
return {
content: [
{
type: 'text',
text: JSON.stringify(formattedResults, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error searching flight inspiration:', error);
return {
content: [
{
type: 'text',
text: `Error searching flight inspiration: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/**
* Tool to search for airport routes
*/
interface AirportRoutesParams {
[key: string]: string | number | undefined;
departureAirportCode: string;
max?: number;
}
interface AirportRoute {
type: string;
subtype: string;
name: string;
iataCode: string;
distance: {
value: number;
unit: string;
};
analytics?: {
flights?: {
score?: number;
};
travelers?: {
score?: number;
};
};
}
interface AirportRoutesResponse {
data: AirportRoute[];
}
server.tool(
'airport-routes',
'Find direct routes from a specific airport',
{
departureAirportCode: z
.string()
.length(3)
.describe('Departure airport IATA code (e.g., JFK)'),
maxResults: z
.number()
.min(1)
.max(100)
.optional()
.default(10)
.describe('Maximum number of results'),
},
async ({ departureAirportCode, maxResults }) => {
try {
const params: AirportRoutesParams = {
departureAirportCode,
max: maxResults,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
const response = (await amadeus.airport.directDestinations.get(
params,
)) as AirportRoutesResponse;
// Format the response for better readability
const formattedResults = response.data.map((route) => ({
destination: route.iataCode,
name: route.name,
type: route.subtype,
distance: `${route.distance.value} ${route.distance.unit}`,
flightScore: route.analytics?.flights?.score || 'N/A',
travelerScore: route.analytics?.travelers?.score || 'N/A',
}));
return {
content: [
{
type: 'text',
text: JSON.stringify(formattedResults, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error searching airport routes:', error);
return {
content: [
{
type: 'text',
text: `Error searching airport routes: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
/**
* Tool to find nearest relevant airports
*/
interface NearestAirportParams {
[key: string]: string | number | undefined;
latitude: number;
longitude: number;
radius?: number;
max?: number;
}
interface NearestAirport {
type: string;
subtype: string;
name: string;
detailedName: string;
iataCode: string;
distance: {
value: number;
unit: string;
};
analytics?: {
flights?: {
score?: number;
};
travelers?: {
score?: number;
};
};
}
interface NearestAirportResponse {
data: NearestAirport[];
}
server.tool(
'nearest-airports',
'Find nearest relevant airports to a specific location',
{
latitude: z
.number()
.min(-90)
.max(90)
.describe('Latitude of the location'),
longitude: z
.number()
.min(-180)
.max(180)
.describe('Longitude of the location'),
radius: z
.number()
.optional()
.default(500)
.describe('Search radius in kilometers'),
maxResults: z
.number()
.min(1)
.max(100)
.optional()
.default(10)
.describe('Maximum number of results'),
},
async ({ latitude, longitude, radius, maxResults }) => {
try {
const params: NearestAirportParams = {
latitude,
longitude,
radius,
max: maxResults,
};
// Remove undefined values
for (const key of Object.keys(params)) {
if (params[key] === undefined) {
delete params[key];
}
}
const response = (await amadeus.referenceData.locations.airports.get(
params,
)) as NearestAirportResponse;
// Format the response for better readability
const formattedResults = response.data.map((airport) => ({
code: airport.iataCode,
name: airport.name,
detailedName: airport.detailedName,
type: airport.subtype,
distance: `${airport.distance.value} ${airport.distance.unit}`,
flightScore: airport.analytics?.flights?.score || 'N/A',
travelerScore: airport.analytics?.travelers?.score || 'N/A',
}));
return {
content: [
{
type: 'text',
text: JSON.stringify(formattedResults, null, 2),
},
],
};
} catch (error: unknown) {
console.error('Error finding nearest airports:', error);
return {
content: [
{
type: 'text',
text: `Error finding nearest airports: ${
error instanceof Error ? error.message : 'Unknown error'
}`,
},
],
isError: true,
};
}
},
);
```
--------------------------------------------------------------------------------
/api-spectifications/FlightCheapestDateSearch_v1_Version_1.0_swagger_specification.json:
--------------------------------------------------------------------------------
```json
{
"swagger": "2.0",
"info": {
"version": "1.0.6",
"title": "Flight Cheapest Date Search",
"x-tags": [
"#ama-for-dev"
],
"x-status": "validated",
"x-release-note": {
"1.0.0": [
"Initial Version"
],
"1.0.1": [
"Clarified example for the parameters origin, destination and currency",
"Added links to ISO, IATA kind of references"
],
"1.0.2": [
"Update example"
],
"1.0.3": [
"Hide currency parameter"
],
"1.0.4": [
"Update example",
"Change link of FlightOffer from v1 to v2"
]
},
"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, to see what is included in test please refer to our **[data collection](https://github.com/amadeus4dev/data-collection)**.\n"
},
"host": "test.api.amadeus.com",
"basePath": "/v1",
"schemes": [
"https"
],
"consumes": [
"application/vnd.amadeus+json",
"application/json"
],
"produces": [
"application/vnd.amadeus+json",
"application/json"
],
"paths": {
"/shopping/flight-dates": {
"get": {
"tags": [
"flight-dates"
],
"operationId": "getFlightDates",
"summary": "Find the cheapest flight dates from an origin to a destination.",
"parameters": [
{
"name": "origin",
"in": "query",
"description": "IATA code of the city from which the flight will depart\n\n[IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) - e.g. MAD for Madrid\n",
"required": true,
"type": "string",
"pattern": "[A-Z]{3}",
"x-example": "MAD"
},
{
"name": "destination",
"in": "query",
"description": "IATA code of the city to which the flight is going.\n\n[IATA table codes](http://www.iata.org/publications/Pages/code-search.aspx) - e.g. MUC for Munich\n",
"required": true,
"type": "string",
"pattern": "[A-Z]{3}",
"x-example": "MUC"
},
{
"name": "departureDate",
"in": "query",
"description": "the date, or range of dates, on which the flight will depart from the origin. Dates are specified in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) YYYY-MM-DD format, e.g. 2017-12-25. Ranges are specified with a comma and are inclusive",
"required": false,
"type": "string"
},
{
"name": "oneWay",
"in": "query",
"description": "if this parameter is set to true, only one-way flights are considered. If this parameter is not set or set to false, only round-trip flights are considered",
"required": false,
"type": "boolean",
"default": false
},
{
"name": "duration",
"in": "query",
"description": "exact duration or range of durations of the travel, in days. This parameter must not be set if oneWay is true. Ranges are specified with a comma and are inclusive, e.g. 2,8",
"required": false,
"type": "string"
},
{
"name": "nonStop",
"in": "query",
"description": "if this parameter is set to true, only flights going from the origin to the destination with no stop in-between are considered",
"required": false,
"type": "boolean",
"default": false
},
{
"name": "maxPrice",
"in": "query",
"description": "defines the price limit for each offer returned. The value should be a positive number, without decimals",
"required": false,
"type": "integer",
"format": "int64",
"minimum": 0
},
{
"name": "viewBy",
"in": "query",
"description": "view the flight dates by DATE, DURATION, or WEEK. View by DATE (default when oneWay is true) to get the cheapest flight dates for every departure date in the given range. View by DURATION (default when oneWay is false) to get the cheapest flight dates for every departure date and for every duration in the given ranges. View by WEEK to get the cheapest flight destination for every week in the given range of departure dates. Note that specifying a detailed view but large ranges may result in a huge number of flight dates being returned. For some very large numbers of flight dates, the API may refuse to provide a response",
"required": false,
"type": "string",
"enum": [
"DATE",
"DURATION",
"WEEK"
]
}
],
"responses": {
"200": {
"$ref": "#/responses/200"
},
"400": {
"$ref": "#/responses/400"
},
"404": {
"$ref": "#/responses/404"
},
"500": {
"$ref": "#/responses/500"
}
},
"description": ""
}
}
},
"definitions": {
"Links": {
"properties": {
"self": {
"type": "string",
"format": "uri"
}
}
},
"Price": {
"properties": {
"total": {
"description": "Total amount paid by the user",
"type": "string",
"example": "932.70"
}
}
},
"LocationDictionary": {
"additionalProperties": {
"$ref": "#/definitions/LocationValue"
}
},
"LocationValue": {
"properties": {
"subType": {
"type": "string",
"description": "location type: airport or city",
"enum": [
"AIRPORT",
"CITY"
],
"example": "AIRPORT"
},
"detailedName": {
"type": "string",
"description": "name of the location",
"example": "Paris/FR: Charles de Gaulle"
}
}
},
"CurrencyDictionary": {
"additionalProperties": {
"type": "string",
"example": "EUR"
}
},
"FlightDates": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/FlightDate"
}
},
"dictionaries": {
"$ref": "#/definitions/Dictionaries"
},
"meta": {
"$ref": "#/definitions/Meta"
},
"warnings": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
},
"example": [
{
"status": "200",
"code": "12345",
"title": "WARNING"
}
]
}
},
"example": {
"data": [
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-07-30",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-07-30&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-07-31",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-07-31&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-01",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-01&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-02",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-02&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-03",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-03&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-04",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-04&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-05",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-05&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-06",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-06&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-07",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-07&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-08",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-08&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-09",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-09&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-07-29",
"returnDate": "2020-08-10",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-07-29&returnDate=2020-08-10&adults=1&nonStop=false"
}
},
{
"type": "flight-date",
"origin": "MAD",
"destination": "MUC",
"departureDate": "2020-09-29",
"returnDate": "2020-09-30",
"price": {
"total": "98.53"
},
"links": {
"flightDestinations": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=MAD&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION",
"flightOffers": "https://test.api.amadeus.com/v2/shopping/flight-offers?originLocationCode=MAD&destinationLocationCode=MUC&departureDate=2020-09-29&returnDate=2020-09-30&adults=1&nonStop=false"
}
}
],
"dictionaries": {
"currencies": {
"EUR": "EURO"
},
"locations": {
"MAD": {
"subType": "AIRPORT",
"detailedName": "ADOLFO SUAREZ BARAJAS"
},
"MUC": {
"subType": "AIRPORT",
"detailedName": "MUNICH INTERNATIONAL"
}
}
},
"meta": {
"currency": "EUR",
"links": {
"self": "https://test.api.amadeus.com/v1/shopping/flight-dates?origin=MAD&destination=MUC&departureDate=2020-07-24,2021-01-19&oneWay=false&duration=1,15&nonStop=false&viewBy=DURATION"
},
"defaults": {
"departureDate": "2020-07-24,2021-01-19",
"oneWay": false,
"duration": "1,15",
"nonStop": false,
"viewBy": "DURATION"
}
},
"warnings": [
{
"title": "Maximum response size reached"
}
]
}
},
"FlightDate": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "the resource name",
"example": "flight-date"
},
"origin": {
"type": "string",
"example": "PAR"
},
"destination": {
"type": "string",
"example": "DXB"
},
"departureDate": {
"type": "string",
"example": "2017-08-19"
},
"returnDate": {
"type": "string",
"example": "2017-08-22"
},
"price": {
"$ref": "#/definitions/Price"
},
"links": {
"type": "object",
"properties": {
"flightDestinations": {
"type": "string",
"example": "https://test.api.amadeus.com/v1/shopping/flight-destinations?origin=PAR&nonStop=true"
},
"flightOffers": {
"type": "string",
"example": "https://test.api.amadeus.com/v1/shopping/flight-offers?origin=PAR&destination=MAD&departureDate=2017-08-26&returnDate=2017-08-30&adults=1"
}
}
}
}
},
"Dictionaries": {
"type": "object",
"properties": {
"currencies": {
"$ref": "#/definitions/CurrencyDictionary"
},
"locations": {
"$ref": "#/definitions/LocationDictionary"
}
}
},
"Meta": {
"type": "object",
"properties": {
"currency": {
"type": "string",
"description": "the currency in which the prices of the flight offers are returned. Currency is specified in the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) format, e.g. EUR for Euro",
"pattern": "[A-Z]{3}",
"example": "EUR"
},
"links": {
"$ref": "#/definitions/Links"
},
"defaults": {
"$ref": "#/definitions/Defaults"
}
}
},
"Defaults": {
"description": "the query parameters for which default values were used are returned here",
"type": "object",
"properties": {
"departureDate": {
"description": "the date, or range of dates, on which the flight will depart from the origin. Dates are specified in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) YYYY-MM-DD format, e.g. 2017-12-25. Ranges are specified with a comma and are inclusive",
"type": "string",
"example": "2011-09-10,2011-09-15"
},
"oneWay": {
"description": "if this parameter is set to true, only one-way flights are considered. If this parameter is not set or set to false, only round-trip flights are considered",
"type": "boolean",
"example": true
},
"duration": {
"description": "exact duration or range of durations of the travel, in days. This parameter must not be set if oneWay is true. Ranges are specified with a comma and are inclusive, e.g. 2,8",
"type": "string",
"example": "2,5"
},
"nonStop": {
"description": "if this parameter is set to true, only flights going from the origin to the destination with no stop in-between are considered",
"type": "boolean",
"example": true
},
"viewBy": {
"type": "string",
"description": "view the flight dates by DATE, DURATION, or WEEK. View by DATE to get the cheapest flight dates for every departure date in the given range. View by DURATION to get the cheapest flight dates for every departure date and for every duration in the given ranges. View by WEEK to get the cheapest flight date for every week in the given range of departure dates",
"enum": [
"DATE",
"DURATION",
"WEEK"
]
}
}
},
"Error_400": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 400,
"code": 477,
"title": "INVALID FORMAT",
"detail": "invalid query parameter format",
"source": {
"parameter": "airport",
"example": "CDG"
}
}
]
}
},
"Error_404": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 404,
"code": 1797,
"title": "NOT FOUND",
"detail": "no response found for this query parameter",
"source": {
"parameter": "airport"
}
}
]
}
},
"Error_500": {
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Issue"
}
}
},
"required": [
"errors"
],
"example": {
"errors": [
{
"status": 500,
"code": 141,
"title": "SYSTEM ERROR HAS OCCURRED"
}
]
}
},
"Issue": {
"properties": {
"status": {
"description": "the HTTP status code applicable to this error",
"type": "integer"
},
"code": {
"description": "an application-specific error code",
"type": "integer",
"format": "int64"
},
"title": {
"description": "a short summary of the error",
"type": "string"
},
"detail": {
"description": "explanation of the error",
"type": "string"
},
"source": {
"type": "object",
"title": "Issue_Source",
"description": "an object containing references to the source of the error",
"maxProperties": 1,
"properties": {
"pointer": {
"description": "a JSON Pointer [RFC6901] to the associated entity in the request document",
"type": "string"
},
"parameter": {
"description": "a string indicating which URI query parameter caused the issue",
"type": "string"
},
"example": {
"description": "a string indicating an example of the right value",
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/FlightDates"
}
},
"400": {
"description": "code | title \n------- | ------------------------------------- \n425 | INVALID DATE\n477 | INVALID FORMAT\n2668 | PARAMETER COMBINATION INVALID/RESTRICTED\n4926 | INVALID DATA RECEIVED\n32171 | MANDATORY DATA MISSING\n",
"schema": {
"$ref": "#/definitions/Error_400"
}
},
"404": {
"description": "code | title \n------- | ------------------------------------- \n6003 | ITEM/DATA NOT FOUND OR DATA NOT EXISTING\n",
"schema": {
"$ref": "#/definitions/Error_404"
}
},
"500": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error_500"
}
}
},
"x-generatedAt": "2020-07-23T08:39:43.848Z"
}
```