Skip to main content
Glama

AMC MCP Server

by hi5d
server.py•20.7 kB
""" AMC MCP Server - Main server implementation """ import asyncio import json import logging import sys from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, List, Optional, Union import uuid from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import ( CallToolRequest, CallToolResult, ListToolsRequest, ListToolsResult, Tool, TextContent, EmbeddedResource ) from pydantic import BaseModel, Field # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Data models class Movie(BaseModel): movie_id: str title: str rating: str duration: int # minutes genre: str description: str poster_url: str class Theater(BaseModel): theater_id: str name: str address: str city: str state: str zip_code: str class Showtime(BaseModel): showtime_id: str movie_id: str theater_id: str date: str time: str format: str # "Standard", "IMAX", "3D", "Dolby" price: float class Seat(BaseModel): seat_number: str row: str column: int is_available: bool price_tier: str # "Standard", "Premium", "Recliner" class Booking(BaseModel): booking_id: str showtime_id: str seats: List[str] user_id: str status: str # "pending", "confirmed", "cancelled" total_price: float created_at: str class Payment(BaseModel): payment_id: str booking_id: str amount: float payment_method: str status: str # "pending", "success", "failed" receipt_url: Optional[str] = None class AMCMCPServer: """AMC MCP Server implementation""" def __init__(self): self.server = Server("amc-mcp") self.data_dir = Path(__file__).parent.parent.parent / "data" self.bookings: Dict[str, Booking] = {} self.payments: Dict[str, Payment] = {} self._load_mock_data() self._setup_handlers() def _load_mock_data(self): """Load mock data from JSON files""" try: with open(self.data_dir / "movies.json", "r") as f: self.movies = {m["movie_id"]: Movie(**m) for m in json.load(f)} with open(self.data_dir / "theaters.json", "r") as f: self.theaters = {t["theater_id"]: Theater(**t) for t in json.load(f)} with open(self.data_dir / "showtimes.json", "r") as f: self.showtimes = {s["showtime_id"]: Showtime(**s) for s in json.load(f)} with open(self.data_dir / "seats.json", "r") as f: self.seats_data = json.load(f) except Exception as e: logger.error(f"Error loading mock data: {e}") self.movies = {} self.theaters = {} self.showtimes = {} self.seats_data = {} def _setup_handlers(self): """Setup MCP server handlers""" @self.server.list_tools() async def list_tools() -> ListToolsResult: """List available tools""" return ListToolsResult( tools=[ Tool( name="get_now_showing", description="Returns a list of movies currently showing in a given city or ZIP code", inputSchema={ "type": "object", "properties": { "location": {"type": "string", "description": "City, state or ZIP code"} }, "required": ["location"] } ), Tool( name="get_recommendations", description="Suggests movies based on mood, genre, or time preferences", inputSchema={ "type": "object", "properties": { "genre": {"type": "string", "description": "Movie genre (optional)"}, "mood": {"type": "string", "description": "Mood description (optional)"}, "time_preference": {"type": "string", "description": "Time of day preference (optional)"} } } ), Tool( name="get_showtimes", description="Fetches available showtimes for a specific movie and location", inputSchema={ "type": "object", "properties": { "movie_id": {"type": "string", "description": "Movie ID"}, "date": {"type": "string", "description": "Date in YYYY-MM-DD format"}, "location": {"type": "string", "description": "City, state or ZIP code"} }, "required": ["movie_id", "date", "location"] } ), Tool( name="get_seat_map", description="Displays available and reserved seats for a specific showtime", inputSchema={ "type": "object", "properties": { "showtime_id": {"type": "string", "description": "Showtime ID"} }, "required": ["showtime_id"] } ), Tool( name="book_seats", description="Reserves selected seats for the user", inputSchema={ "type": "object", "properties": { "showtime_id": {"type": "string", "description": "Showtime ID"}, "seats": {"type": "array", "items": {"type": "string"}, "description": "List of seat numbers (e.g., ['A5', 'A6'])"}, "user_id": {"type": "string", "description": "User identifier"} }, "required": ["showtime_id", "seats", "user_id"] } ), Tool( name="process_payment", description="Handles simulated payment transaction", inputSchema={ "type": "object", "properties": { "booking_id": {"type": "string", "description": "Booking ID"}, "payment_method": {"type": "string", "description": "Payment method (card, cash, etc.)"}, "amount": {"type": "number", "description": "Payment amount"} }, "required": ["booking_id", "payment_method", "amount"] } ) ] ) @self.server.call_tool() async def call_tool(request: CallToolRequest) -> CallToolResult: """Handle tool calls""" try: if request.name == "get_now_showing": return await self._get_now_showing(request.arguments) elif request.name == "get_recommendations": return await self._get_recommendations(request.arguments) elif request.name == "get_showtimes": return await self._get_showtimes(request.arguments) elif request.name == "get_seat_map": return await self._get_seat_map(request.arguments) elif request.name == "book_seats": return await self._book_seats(request.arguments) elif request.name == "process_payment": return await self._process_payment(request.arguments) else: return CallToolResult( content=[TextContent(type="text", text=f"Unknown tool: {request.name}")] ) except Exception as e: logger.error(f"Error calling tool {request.name}: {e}") return CallToolResult( content=[TextContent(type="text", text=f"Error: {str(e)}")] ) async def _get_now_showing(self, args: Dict[str, Any]) -> CallToolResult: """Get movies currently showing in a location""" location = args.get("location", "") # Filter movies by location (simplified - match any theater in the area) showing_movies = [] for movie in self.movies.values(): # Simple mock logic - show all movies for any location movie_data = { "movie_id": movie.movie_id, "title": movie.title, "rating": movie.rating, "duration": movie.duration, "genre": movie.genre, "description": movie.description } showing_movies.append(movie_data) result = { "location": location, "movies": showing_movies[:10] # Limit to 10 movies } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def _get_recommendations(self, args: Dict[str, Any]) -> CallToolResult: """Get movie recommendations based on preferences""" genre = args.get("genre", "").lower() mood = args.get("mood", "").lower() recommendations = [] for movie in self.movies.values(): # Simple matching logic if genre and genre in movie.genre.lower(): recommendations.append({ "movie_id": movie.movie_id, "title": movie.title, "genre": movie.genre, "description": movie.description, "rating": movie.rating }) elif mood and (mood in movie.description.lower() or mood in movie.genre.lower()): recommendations.append({ "movie_id": movie.movie_id, "title": movie.title, "genre": movie.genre, "description": movie.description, "rating": movie.rating }) if not recommendations and not genre and not mood: # Return top picks if no specific criteria recommendations = [ { "movie_id": movie.movie_id, "title": movie.title, "genre": movie.genre, "description": movie.description, "rating": movie.rating } for movie in list(self.movies.values())[:5] ] result = { "criteria": {"genre": genre, "mood": mood}, "recommendations": recommendations[:5] } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def _get_showtimes(self, args: Dict[str, Any]) -> CallToolResult: """Get showtimes for a movie on a specific date and location""" movie_id = args.get("movie_id") date = args.get("date") location = args.get("location") if not movie_id or movie_id not in self.movies: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": "Invalid movie ID"}))] ) movie = self.movies[movie_id] showtimes = [] for showtime in self.showtimes.values(): if showtime.movie_id == movie_id and showtime.date == date: theater = self.theaters.get(showtime.theater_id) if theater: showtimes.append({ "showtime_id": showtime.showtime_id, "theater_name": theater.name, "theater_address": theater.address, "time": showtime.time, "format": showtime.format, "price": showtime.price }) result = { "movie": {"id": movie.movie_id, "title": movie.title}, "date": date, "location": location, "showtimes": showtimes } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def _get_seat_map(self, args: Dict[str, Any]) -> CallToolResult: """Get seat map for a showtime""" showtime_id = args.get("showtime_id") if not showtime_id or showtime_id not in self.showtimes: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": "Invalid showtime ID"}))] ) # Get seats for this showtime (mock data) seats = self.seats_data.get(showtime_id, []) seat_map = [] for seat_data in seats: # Check if seat is already booked is_booked = any( seat_data["seat_number"] in booking.seats and booking.status == "confirmed" for booking in self.bookings.values() if booking.showtime_id == showtime_id ) seat_map.append({ "seat_number": seat_data["seat_number"], "row": seat_data["row"], "column": seat_data["column"], "is_available": not is_booked, "price_tier": seat_data["price_tier"], "price": seat_data.get("price", 15.00) }) showtime = self.showtimes[showtime_id] theater = self.theaters.get(showtime.theater_id) movie = self.movies.get(showtime.movie_id) result = { "showtime_id": showtime_id, "movie": movie.title if movie else "Unknown", "theater": theater.name if theater else "Unknown Theater", "date": showtime.date, "time": showtime.time, "seat_map": seat_map } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def _book_seats(self, args: Dict[str, Any]) -> CallToolResult: """Book seats for a showtime""" showtime_id = args.get("showtime_id") seats = args.get("seats", []) user_id = args.get("user_id") if not showtime_id or showtime_id not in self.showtimes: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": "Invalid showtime ID"}))] ) if not seats or not user_id: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": "Seats and user_id are required"}))] ) # Check seat availability unavailable_seats = [] total_price = 0.0 showtime_seats = self.seats_data.get(showtime_id, []) seat_lookup = {s["seat_number"]: s for s in showtime_seats} for seat_num in seats: # Check if seat exists if seat_num not in seat_lookup: unavailable_seats.append(f"{seat_num} (doesn't exist)") continue # Check if already booked is_booked = any( seat_num in booking.seats and booking.status == "confirmed" for booking in self.bookings.values() if booking.showtime_id == showtime_id ) if is_booked: unavailable_seats.append(f"{seat_num} (already booked)") else: seat_price = seat_lookup[seat_num].get("price", 15.00) total_price += seat_price if unavailable_seats: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": f"Unavailable seats: {', '.join(unavailable_seats)}"}))] ) # Create booking booking_id = str(uuid.uuid4()) booking = Booking( booking_id=booking_id, showtime_id=showtime_id, seats=seats, user_id=user_id, status="pending", total_price=total_price, created_at=datetime.now().isoformat() ) self.bookings[booking_id] = booking showtime = self.showtimes[showtime_id] theater = self.theaters.get(showtime.theater_id) movie = self.movies.get(showtime.movie_id) result = { "booking_id": booking_id, "status": "pending", "movie": movie.title if movie else "Unknown", "theater": theater.name if theater else "Unknown Theater", "date": showtime.date, "time": showtime.time, "seats": seats, "total_price": total_price } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def _process_payment(self, args: Dict[str, Any]) -> CallToolResult: """Process payment for a booking""" booking_id = args.get("booking_id") payment_method = args.get("payment_method") amount = args.get("amount") if not booking_id or booking_id not in self.bookings: return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": "Invalid booking ID"}))] ) booking = self.bookings[booking_id] if booking.status != "pending": return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": f"Booking status is {booking.status}, expected pending"}))] ) if abs(amount - booking.total_price) > 0.01: # Allow for small rounding differences return CallToolResult( content=[TextContent(type="text", text=json.dumps({"error": f"Amount mismatch. Expected ${booking.total_price:.2f}, got ${amount:.2f}"}))] ) # Simulate payment processing (always succeeds in mock) payment_id = str(uuid.uuid4()) receipt_url = f"https://amc.com/receipts/{payment_id}" payment = Payment( payment_id=payment_id, booking_id=booking_id, amount=amount, payment_method=payment_method, status="success", receipt_url=receipt_url ) self.payments[payment_id] = payment # Update booking status booking.status = "confirmed" showtime = self.showtimes[booking.showtime_id] theater = self.theaters.get(showtime.theater_id) movie = self.movies.get(showtime.movie_id) result = { "payment_id": payment_id, "payment_status": "success", "booking_id": booking_id, "receipt_url": receipt_url, "confirmation": { "movie": movie.title if movie else "Unknown", "theater": theater.name if theater else "Unknown Theater", "date": showtime.date, "time": showtime.time, "seats": booking.seats, "total_paid": amount } } return CallToolResult( content=[TextContent(type="text", text=json.dumps(result, indent=2))] ) async def run(self): """Run the MCP server""" async with stdio_server() as (read_stream, write_stream): await self.server.run( read_stream, write_stream, self.server.create_initialization_options() ) def main(): """Main entry point""" server = AMCMCPServer() asyncio.run(server.run()) if __name__ == "__main__": main()

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/hi5d/amc-mcp'

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