text-adventure-template / mcp_server.py
Alejandro Arguelles
copied good agent code
4ee5784
"""
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
# 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
# =============================================================================
# TODO: Create a FastMCP server instance
# Hint: mcp = FastMCP("Your Server Name")
mcp = FastMCP("Text Adventure Server")
# =============================================================================
# Game State Management
# =============================================================================
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:
# Extract name part: "Obj180: West House Parent..." -> "West House"
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()
# Track additional state
self.history = []
self.explored_locations = {}
self.summary = "No detailed memory yet. I have just started the game."
# Initial State Clean
if self.state.location:
self.state.location = self._clean_jericho_name(self.state.location)
# Add initial location to map
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)
# Clean location
if self.state.location:
self.state.location = self._clean_jericho_name(self.state.location)
# Track explored locations
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
# Global game instance (created on first use)
_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 Tools - IMPLEMENT THESE!
# =============================================================================
@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
"""
# TODO: Implement this tool
# Hint: Use get_game().take_action(action)
game = get_game()
result = game.take_action(action)
# TODO: Optionally add score info or game over detection
return result
# TODO: Implement additional helper tools
# These are optional but will help your agent play better!
# @mcp.tool()
# def memory() -> str:
# """
# Get a summary of the current game state.
#
# Returns location, score, recent actions, and current observation.
# Use this to understand where you are and what happened recently.
# """
# # TODO: Implement this
# pass
@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."
# Clean item names
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)
# =============================================================================
# Main - Run the server
# =============================================================================
if __name__ == "__main__":
# This runs the server using stdio transport (for local testing)
mcp.run()