import os import sys # Add src directory to Python path for Hugging Face Spaces compatibility sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) import logging import uuid import gradio as gr import spaces from unpredictable_lord.chat import chat_with_llm_stream from unpredictable_lord.game_state import ( ADVICE_DESCRIPTIONS, PERSONALITY_DESCRIPTIONS, create_session, get_available_advice, get_session, ) logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger(__name__) logger.info(f"ZeroGPU: {spaces.config.Config.zero_gpu}") def init_game(personality: str = "cautious") -> dict: """ Initialize a new game session and return the session information. This MCP tool creates a new game session with the specified lord personality. Use the returned session_id for all subsequent game operations. Args: personality: The lord's personality type. Options: "cautious" (risk-averse), "idealist" (emotional idealist), "populist" (popularity-focused). Defaults to "cautious". Returns: dict: Session information including session_id and initial game state. """ session_id = str(uuid.uuid4()) # Validate personality if personality not in PERSONALITY_DESCRIPTIONS: personality = "cautious" # Create game session using game_state module state = create_session(session_id, personality) logger.info( f"New game session created: {session_id} with personality: {personality}" ) return { "session_id": session_id, "message": f"New game started! You are now the advisor to a {personality} lord.", "personality_description": PERSONALITY_DESCRIPTIONS[personality], "available_advice": ADVICE_DESCRIPTIONS, "game_state": state.to_dict(), } def get_game_state(session_id: str) -> dict: """ Get the current game state for a session. This MCP tool retrieves the current state of a game session, including all parameters, turn number, and game status. Args: session_id: The session ID returned from init_game. Returns: dict: Current game state or error message if session not found. """ state = get_session(session_id) if state is None: return { "error": "Session not found", "message": f"No game session found with ID: {session_id}. Please call init_game first.", } return { "session_id": session_id, "game_state": state.to_dict(), "status_summary": state.get_status_summary(), } def list_available_advice() -> dict: """ Get all available advice options that can be given to the lord. This MCP tool returns a list of all possible advice types that can be used with execute_turn(). The Lord AI should interpret the user's free-form suggestion and map it to one of these options. Returns: dict: Dictionary containing all available advice options with their names, descriptions, and expected effects. """ return { "advice_options": get_available_advice(), "usage": "Interpret the user's advice and select the most appropriate option from the list above.", } # Gradio UI with gr.Blocks(title="Unpredictable Lord") as demo: gr.Markdown("# Unpredictable Lord\nLord Advisor AI Simulation") with gr.Tabs(): # Chat Tab with gr.TabItem("Chat"): chatbot = gr.Chatbot(label="Lord AI", height=600, type="messages") with gr.Row(): msg = gr.Textbox( label="Your Advice", placeholder="My Lord, I have a proposal...", scale=4, ) submit_btn = gr.Button("Submit", scale=1) clear = gr.Button("Clear History") def user(user_message, history): # Append user message to history in messages format return "", history + [{"role": "user", "content": user_message}] def bot(history): # The last message is the user's message user_message = history[-1]["content"] history_for_model = history[:-1] for updated_history in chat_with_llm_stream( user_message, history_for_model ): yield updated_history msg.submit( user, [msg, chatbot], [msg, chatbot], queue=False, show_api=False ).then(bot, chatbot, chatbot, show_api=False) submit_btn.click( user, [msg, chatbot], [msg, chatbot], queue=False, show_api=False ).then(bot, chatbot, chatbot, show_api=False) clear.click(lambda: None, None, chatbot, queue=False, show_api=False) # MCP Server Tab with gr.TabItem("MCP Server"): gr.Markdown( """ ## MCP Server Guide This application functions as an **MCP (Model Context Protocol) Server**. External LLMs can connect to this server and use game management tools. ### Connection URL ``` https://.hf.space/gradio_api/mcp/ ``` For local development: ``` http://localhost:7860/gradio_api/mcp/ ``` ### How to Connect #### Claude Desktop / Cursor / VS Code Add the following to your MCP settings configuration: ```json { "mcpServers": { "unpredictable-lord": { "url": "https://.hf.space/gradio_api/mcp/" } } } ``` ### Available Tools | Tool | Description | |------|-------------| | `init_game` | Initialize a new game session. Returns a session_id and available advice options. | | `get_game_state` | Get the current game state for a session. | | `list_available_advice` | Get all available advice options for execute_turn. | ### Usage Flow 1. Call `init_game(personality)` to start a new game session 2. Use the returned `session_id` for all subsequent game operations 3. Game state is managed server-side """ ) gr.Markdown("### Test: Initialize Game") with gr.Row(): personality_input = gr.Dropdown( choices=["cautious", "idealist", "populist"], value="cautious", label="Lord Personality", ) init_btn = gr.Button("Start New Game") init_output = gr.JSON(label="Game Session Info") init_btn.click(fn=init_game, inputs=personality_input, outputs=init_output) gr.Markdown("### Test: Get Game State") with gr.Row(): session_id_input = gr.Textbox( label="Session ID", placeholder="Enter session_id from init_game", ) get_state_btn = gr.Button("Get State") state_output = gr.JSON(label="Current Game State") get_state_btn.click( fn=get_game_state, inputs=session_id_input, outputs=state_output ) gr.Markdown("### Test: List Available Advice") list_advice_btn = gr.Button("List Advice Options") advice_output = gr.JSON(label="Available Advice Options") list_advice_btn.click( fn=list_available_advice, inputs=[], outputs=advice_output ) if __name__ == "__main__": demo.launch(mcp_server=True)