| """ |
| MCP Server Template for Text Adventure Games |
| |
| This is a starter template for building your text adventure MCP server. |
| Your task is to implement the tools that allow an AI agent to play text adventures. |
| |
| FastMCP makes it easy to create MCP servers - just decorate functions! |
| |
| TODO: |
| 1. Implement the play_action tool (required) |
| 2. Add helper tools like memory, get_map, inventory (recommended) |
| 3. Test your server with: fastmcp dev templates/mcp_server_template.py |
| """ |
|
|
| import sys |
| import os |
|
|
| |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| from fastmcp import FastMCP |
| from games.zork_env import TextAdventureEnv |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| mcp = FastMCP("Text Adventure Server") |
|
|
|
|
| |
| |
| |
|
|
| class GameState: |
| """ |
| Manages the text adventure game state. |
| |
| TODO: You may want to extend this class to track: |
| - Action history (for context) |
| - Explored locations (for mapping) |
| - Current location name |
| """ |
|
|
| def _clean_jericho_name(self, s: str) -> str: |
| """Clean raw Jericho string representations (e.g. 'Obj180: West House Parent...').""" |
| if not s: return s |
| if "Obj" in s and ":" in s: |
| |
| parts = s.split(":", 1)[1].strip() |
| if "Parent" in parts: |
| parts = parts.split("Parent")[0].strip() |
| return parts |
| return s |
| |
| def __init__(self, game: str = "zork1"): |
| self.game_name = game |
| self.env = TextAdventureEnv(game) |
| self.state = self.env.reset() |
| |
| self.history = [] |
| self.explored_locations = {} |
| self.summary = "No detailed memory yet. I have just started the game." |
| |
| |
| if self.state.location: |
| self.state.location = self._clean_jericho_name(self.state.location) |
| |
| self.explored_locations[self.state.location] = 1 |
| |
| def take_action(self, action: str) -> str: |
| """Execute a game action and return the result.""" |
| action = action.strip() |
| self.state = self.env.step(action) |
| self.history.append(action) |
| |
| |
| if self.state.location: |
| self.state.location = self._clean_jericho_name(self.state.location) |
| |
| |
| if self.state.location and self.state.location != "Unknown": |
| count = self.explored_locations.get(self.state.location, 0) |
| self.explored_locations[self.state.location] = count + 1 |
| |
| return self.state.observation |
|
|
|
|
| |
| _game: GameState | None = None |
|
|
|
|
| def get_game() -> GameState: |
| """Get or create the game instance.""" |
| global _game |
| if _game is None: |
| _game = GameState() |
| return _game |
|
|
|
|
| |
| |
| |
|
|
| @mcp.tool() |
| def play_action(action: str) -> str: |
| """ |
| Execute a game action in the text adventure. |
| |
| This is the main tool for interacting with the game. |
| |
| Common commands: |
| - Movement: north, south, east, west, up, down |
| - Objects: take <item>, drop <item>, open <thing> |
| - Look: look, examine <thing> |
| |
| Args: |
| action: The command to execute (e.g., 'north', 'take lamp') |
| |
| Returns: |
| The game's response to your action |
| """ |
| |
| |
| game = get_game() |
| result = game.take_action(action) |
| |
| |
| return result |
|
|
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| @mcp.tool() |
| def inventory() -> str: |
| """List the items currently carried.""" |
| game = get_game() |
| items = game.state.inventory |
| if not items: |
| return "You are not carrying anything." |
| |
| |
| cleaned_items = [game._clean_jericho_name(i) for i in items] |
| return "Carrying:\n" + "\n".join(f"- {item}" for item in cleaned_items) |
|
|
|
|
| @mcp.tool() |
| def get_map() -> str: |
| """List all locations visited so far.""" |
| game = get_game() |
| if not game.explored_locations: |
| return "You haven't explored any locations yet." |
| |
| lines = ["Explored Locations:"] |
| for loc, count in game.explored_locations.items(): |
| mark = " (Current)" if loc == game.state.location else "" |
| lines.append(f"- {loc} [Visited {count} times]{mark}") |
| |
| return "\n".join(lines) |
|
|
|
|
| @mcp.tool() |
| def get_current_state() -> str: |
| """ |
| Get a snapshot of the current game situation. |
| |
| Returns: |
| - Current Observation (what you see) |
| - Score and Moves |
| - Current Memory Notebook content |
| """ |
| game = get_game() |
| info = [ |
| "=== CURRENT GAME STATE ===", |
| f"Score: {game.state.score}/{game.state.max_score}", |
| f"Moves: {game.state.moves}", |
| f"Inventory (Last Known): {[game._clean_jericho_name(i) for i in game.state.inventory]}", |
| "", |
| "--- OBSERVATION ---", |
| game.state.observation, |
| "", |
| "--- NOTEBOOK ---", |
| game.summary |
| ] |
| return "\n".join(info) |
|
|
|
|
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
| |
| mcp.run() |
|
|