""" Student MCP Server for Text Adventure Games This is your MCP server submission. Implement the tools that your agent will use to play text adventure games. Required tool: play_action(action: str) -> str Execute a game command and return the result. Recommended tools: memory() -> str Return current game state, score, and recent history. inventory() -> str Return the player's current inventory. get_map() -> str Return a map of explored locations. Test your server with: fastmcp dev submission_template/mcp_server.py Then open the MCP Inspector in your browser to test the tools interactively. """ """ Student MCP Server for Text Adventure Games """ import sys import os # Add parent directory to path to import games module sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from fastmcp import FastMCP from games.zork_env import TextAdventureEnv # ============================================================================= # Create the MCP Server # ============================================================================= mcp = FastMCP("Student Text Adventure Server") # ============================================================================= # Game State Management # ============================================================================= class GameManager: """Manages the text adventure game state.""" def __init__(self): self.env: TextAdventureEnv = None self.state = None self.game_name: str = "" self.history: list[tuple[str, str]] = [] self.explored_locations: dict[str, set[str]] = {} self.current_location: str = "" self.items_seen: set[str] = set() def initialize(self, game: str = "zork1"): """Initialize or reset the game.""" self.game_name = game self.env = TextAdventureEnv(game) self.state = self.env.reset() self.history = [] self.explored_locations = {} self.current_location = "" self.items_seen = set() self._extract_location(self.state.observation) return self.state.observation def step(self, action: str) -> str: """Execute action and return full observation.""" if not self.env: return "Game not initialized" # Exécuter l'action self.state = self.env.step(action) obs = self.state.observation # NE PAS faire de "look" supplémentaire automatiquement # Le jeu retourne déjà la description après chaque action # Si tu veux forcer un look, l'agent doit le demander explicitement # Track history self.history.append((action, obs)) self._extract_location(obs) return obs def _extract_location(self, observation: str): """Extract and track location from observation.""" lines = observation.strip().split('\n') for line in lines: line = line.strip() if line and not line.startswith('>') and len(line) < 50: # Likely a location name if line[0].isupper(): self.current_location = line if line not in self.explored_locations: self.explored_locations[line] = set() break def get_score(self) -> int: """Get current score.""" return self.state.score if self.state else 0 def get_moves(self) -> int: """Get number of moves taken.""" return self.state.moves if self.state else 0 def get_history_summary(self, n: int = 10) -> str: """Get last n actions and results.""" recent = self.history[-n:] if self.history else [] summary = [] for action, obs in recent: short_obs = obs[:150] + "..." if len(obs) > 150 else obs summary.append(f"> {action}\n{short_obs}") return "\n\n".join(summary) # Global game manager _game = GameManager() def get_game() -> GameManager: """Get or initialize the game manager.""" global _game if _game.env is None: game = os.environ.get("GAME", "zork1") _game.initialize(game) return _game # ============================================================================= # MCP Tools # ============================================================================= @mcp.tool() def play_action(action: str) -> str: """ Execute a game command and return the result. Args: action: The command to execute (e.g., "north", "take lamp", "open mailbox") Returns: The game's response to the action with score info """ game = get_game() result = game.step(action) # Append score info result += f"\n[Score: {game.get_score()} | Moves: {game.get_moves()}]" return result @mcp.tool() def memory() -> str: """ Get the current game state summary. Returns: A summary including current location, score, moves, and recent history """ game = get_game() summary = f"""=== GAME STATE === Game: {game.game_name} Current Location: {game.current_location} Score: {game.get_score()} Moves: {game.get_moves()} === EXPLORED LOCATIONS ({len(game.explored_locations)}) === {', '.join(game.explored_locations.keys()) if game.explored_locations else 'None yet'} === RECENT HISTORY === {game.get_history_summary(5)} """ return summary @mcp.tool() def inventory() -> str: """ Check what the player is carrying. Returns: List of items in the player's inventory """ game = get_game() result = game.step("inventory") return result @mcp.tool() def get_location() -> str: game = get_game() # Si Jericho supporte l'extraction d'état pour ce jeu return str(game.env.get_player_location()) @mcp.tool() def get_valid_actions() -> list[str]: """Get Jericho's list of valid actions for current state.""" # 1. On récupère le gestionnaire de jeu actuel game = get_game() # Sécurité au cas où le jeu n'est pas encore initialisé if not game or not game.env: return ["look", "inventory"] try: import sys import io old_stderr = sys.stderr sys.stderr = io.StringIO() try: # 2. On appelle la fonction sur game.env ! actions = game.env.get_valid_actions() finally: sys.stderr = old_stderr return actions if actions else ["look", "inventory"] except Exception: return ["look", "inventory"] @mcp.tool() def get_score() -> str: """ Get the current score and moves. Returns: Current score and number of moves """ game = get_game() return f"Score: {game.get_score()} | Moves: {game.get_moves()}" # ============================================================================= # Run the server # ============================================================================= if __name__ == "__main__": mcp.run()