Skip to main content
Glama
server.py14.6 kB
import os import json from typing import Optional from amadeus import Client, ResponseError from mcp.server.fastmcp import FastMCP, Context def get_amadeus_client() -> Client: """Create Amadeus client with credentials from environment""" # Support both old and new environment variable names for backward compatibility api_key = os.environ.get("AMADEUS_CLIENT_ID") or os.environ.get("AMADEUS_API_KEY") api_secret = os.environ.get("AMADEUS_CLIENT_SECRET") or os.environ.get("AMADEUS_API_SECRET") hostname = os.environ.get("AMADEUS_HOSTNAME", "test") if not api_key or not api_secret: raise ValueError( "Amadeus API credentials not configured. " "Please provide your Amadeus API credentials when connecting to this server." ) try: # Create the client amadeus_client = Client( client_id=api_key, client_secret=api_secret, hostname=hostname ) return amadeus_client except Exception as e: raise ValueError(f"Failed to initialize Amadeus client: {str(e)}") mcp = FastMCP("Amadeus API", stateless_http=True) # Simple debug tool that doesn't require any credentials @mcp.tool() def ping() -> str: """Simple ping tool to test server connectivity""" return "pong" # Add a simple health check endpoint for container deployments @mcp.custom_route("/health", methods=["GET"]) async def health_check(request): """Health check endpoint for container deployments""" from starlette.responses import JSONResponse return JSONResponse({"status": "healthy", "service": "amadeus-mcp-api"}) # Add debug endpoint to see what parameters we're receiving @mcp.custom_route("/debug", methods=["GET"]) async def debug_endpoint(request): """Debug endpoint to see what parameters are being received""" from starlette.responses import JSONResponse query_params = dict(request.query_params) headers = dict(request.headers) # Get current environment variables env_vars = { "AMADEUS_CLIENT_ID": os.environ.get("AMADEUS_CLIENT_ID", "NOT_SET"), "AMADEUS_CLIENT_SECRET": "SET" if os.environ.get("AMADEUS_CLIENT_SECRET") else "NOT_SET", "AMADEUS_HOSTNAME": os.environ.get("AMADEUS_HOSTNAME", "NOT_SET"), } return JSONResponse({ "message": "Debug info for Amadeus MCP Server", "query_parameters": query_params, "environment_variables": env_vars, "headers": {k: v for k, v in headers.items() if k.lower() in ['host', 'user-agent', 'referer', 'authorization']}, "server_status": "running" }) @mcp.tool() def search_flight_offers( originLocationCode: str, destinationLocationCode: str, departureDate: str, adults: int, ctx: Context, returnDate: Optional[str] = None, children: Optional[int] = None, infants: Optional[int] = None, travelClass: Optional[str] = None, includedAirlineCodes: Optional[str] = None, excludedAirlineCodes: Optional[str] = None, nonStop: Optional[bool] = None, currencyCode: Optional[str] = None, maxPrice: Optional[int] = None, max: int = 250 ) -> str: """ Search for flight offers using the Amadeus API Args: originLocationCode: IATA code of the departure city/airport (e.g., SYD for Sydney) destinationLocationCode: IATA code of the destination city/airport (e.g., BKK for Bangkok) departureDate: Departure date in ISO 8601 format (YYYY-MM-DD, e.g., 2023-05-02) adults: Number of adult travelers (age 12+), must be 1-9 returnDate: Return date in ISO 8601 format (YYYY-MM-DD), if round-trip is desired children: Number of child travelers (age 2-11) infants: Number of infant travelers (age <= 2) travelClass: Travel class (ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST) includedAirlineCodes: Comma-separated IATA airline codes to include (e.g., '6X,7X') excludedAirlineCodes: Comma-separated IATA airline codes to exclude (e.g., '6X,7X') nonStop: If true, only non-stop flights are returned currencyCode: ISO 4217 currency code (e.g., EUR for Euro) maxPrice: Maximum price per traveler, positive integer with no decimals max: Maximum number of flight offers to return """ # Validate input parameters before attempting to get credentials if adults and not (1 <= adults <= 9): return json.dumps({"error": "Adults must be between 1 and 9"}) if children and infants and adults and (adults + children > 9): return json.dumps({"error": "Total number of seated travelers (adults + children) cannot exceed 9"}) if infants and adults and (infants > adults): return json.dumps({"error": "Number of infants cannot exceed number of adults"}) # Try to get the Amadeus client - this is where credentials are validated try: amadeus_client = get_amadeus_client() except ValueError as e: # Return a user-friendly error if credentials are not configured return json.dumps({ "error": "Configuration required", "message": str(e), "details": "Please ensure your Amadeus API credentials are properly configured." }) except Exception as e: # Handle any other initialization errors return json.dumps({ "error": "Service initialization failed", "message": str(e) }) # Build API parameters with proper filtering to avoid Amadeus API 400 errors params = {} params["originLocationCode"] = originLocationCode params["destinationLocationCode"] = destinationLocationCode params["departureDate"] = departureDate params["adults"] = adults # Only include optional parameters when they have meaningful values if returnDate: params["returnDate"] = returnDate # Don't send children/infants if they are 0 - Amadeus API rejects these if children is not None and children > 0: params["children"] = children if infants is not None and infants > 0: params["infants"] = infants if travelClass: params["travelClass"] = travelClass # Don't send empty airline codes - Amadeus API rejects empty strings if includedAirlineCodes and includedAirlineCodes.strip(): params["includedAirlineCodes"] = includedAirlineCodes if excludedAirlineCodes and excludedAirlineCodes.strip(): params["excludedAirlineCodes"] = excludedAirlineCodes # Only send nonStop when it's True - Amadeus API rejects nonStop: false if nonStop is True: params["nonStop"] = nonStop if currencyCode: params["currencyCode"] = currencyCode if maxPrice is not None and maxPrice > 0: params["maxPrice"] = maxPrice if max is not None and max > 0: params["max"] = max # Make the actual API call try: print(f"Searching flights from {originLocationCode} to {destinationLocationCode}") print(f"API parameters: {json.dumps(params)}") response = amadeus_client.shopping.flight_offers_search.get(**params) return json.dumps(response.body) except ResponseError as error: error_msg = f"Amadeus API error: {str(error)}" print(f"Error: {error_msg}") return json.dumps({"error": error_msg}) except Exception as e: error_msg = f"Unexpected error: {str(e)}" print(f"Error: {error_msg}") return json.dumps({"error": error_msg}) @mcp.prompt() def flight_search_prompt(origin: str, destination: str, date: str) -> str: """Create a flight search prompt""" return f""" Please search for flights from {origin} to {destination} on {date}. I'd like to see options sorted by price, with information about the airlines, departure/arrival times, and any layovers. """ if __name__ == "__main__": import os import asyncio print("=== Amadeus MCP Server Starting ===") print(f"Python version: {os.sys.version}") print(f"Working directory: {os.getcwd()}") print(f"Environment variables:") for key in ['MCP_TRANSPORT', 'PORT', 'HOST', 'AMADEUS_CLIENT_ID', 'AMADEUS_CLIENT_SECRET']: value = os.environ.get(key, 'NOT_SET') if 'SECRET' in key: value = 'SET' if value != 'NOT_SET' else 'NOT_SET' print(f" {key}={value}") # Check if we should run with HTTP transport (for streamable HTTP) transport = os.environ.get("MCP_TRANSPORT", "stdio") if transport == "http": # Run with streamable HTTP transport # Use PORT environment variable (set by Smithery) or default to 8000 port = int(os.environ.get("PORT", 8000)) host = os.environ.get("HOST", "0.0.0.0") # Use 0.0.0.0 for container deployments print(f"Starting Amadeus MCP server in HTTP mode") print(f"Server will bind to: {host}:{port}") print("Available tools: ping, search_flight_offers") try: async def run_http(): print("Creating HTTP app with middleware...") # Create the HTTP app and add middleware for query parameter parsing app = mcp.streamable_http_app() # Add middleware to parse query parameters and set environment variables @app.middleware("http") async def config_middleware(request, call_next): # Extract configuration from query parameters query_params = dict(request.query_params) # Debug logging - show ALL query parameters received print(f"🔍 DEBUG: Request path: {request.url.path}") print(f"🔍 DEBUG: Query parameters received: {query_params}") # Check for Smithery's base64-encoded config parameter if 'config' in query_params: try: import base64 import json # Decode the base64-encoded JSON config config_b64 = query_params['config'] decoded_bytes = base64.b64decode(config_b64) decoded_string = decoded_bytes.decode('utf-8') config_data = json.loads(decoded_string) print(f"✅ Decoded Smithery config parameter") print(f"🔍 Config contains: {list(config_data.keys())}") # Apply Amadeus configuration from decoded config if 'amadeusClientId' in config_data: os.environ['AMADEUS_CLIENT_ID'] = config_data['amadeusClientId'] print(f"✅ Applied AMADEUS_CLIENT_ID from Smithery config") if 'amadeusClientSecret' in config_data: os.environ['AMADEUS_CLIENT_SECRET'] = config_data['amadeusClientSecret'] print(f"✅ Applied AMADEUS_CLIENT_SECRET from Smithery config") if 'amadeusHostname' in config_data: os.environ['AMADEUS_HOSTNAME'] = config_data['amadeusHostname'] print(f"✅ Applied AMADEUS_HOSTNAME: {config_data['amadeusHostname']}") else: # Default to test environment if not specified os.environ['AMADEUS_HOSTNAME'] = 'test' print(f"ℹ️ Using default AMADEUS_HOSTNAME: test") except Exception as e: print(f"❌ Error parsing Smithery config: {e}") # Fallback: Check for individual query parameters (for backwards compatibility) else: print(f"🔍 No 'config' parameter found, checking individual parameters...") if 'amadeusClientId' in query_params: os.environ['AMADEUS_CLIENT_ID'] = query_params['amadeusClientId'] print(f"✅ Applied AMADEUS_CLIENT_ID from individual query params") else: print(f"❌ amadeusClientId NOT found in query params") if 'amadeusClientSecret' in query_params: os.environ['AMADEUS_CLIENT_SECRET'] = query_params['amadeusClientSecret'] print(f"✅ Applied AMADEUS_CLIENT_SECRET from individual query params") else: print(f"❌ amadeusClientSecret NOT found in query params") if 'amadeusHostname' in query_params: os.environ['AMADEUS_HOSTNAME'] = query_params['amadeusHostname'] print(f"✅ Applied AMADEUS_HOSTNAME: {query_params['amadeusHostname']}") else: print(f"ℹ️ amadeusHostname not specified, using default: test") os.environ['AMADEUS_HOSTNAME'] = 'test' # Continue processing the request response = await call_next(request) return response # Start the server with uvicorn import uvicorn config = uvicorn.Config(app, host=host, port=port, log_level="info") server = uvicorn.Server(config) print("Starting HTTP server with query parameter support...") await server.serve() asyncio.run(run_http()) except Exception as e: print(f"ERROR: Failed to start HTTP server: {e}") import traceback traceback.print_exc() else: print("Starting in STDIO mode") try: mcp.run() except Exception as e: print(f"ERROR: Failed to start STDIO server: {e}") import traceback traceback.print_exc()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/almogqwinz/mcp-amadeus-api'

If you have feedback or need assistance with the MCP directory API, please join our Discord server