Spaces:
Sleeping
Sleeping
| from collections.abc import AsyncIterator | |
| from contextlib import asynccontextmanager | |
| from dataclasses import dataclass, field | |
| from typing import Any, Union | |
| from mcp.server.fastmcp import Context, FastMCP | |
| from mcp.server.session import ServerSession | |
| Observation = Union[str, dict[str, Any]] | |
| Action = Union[str, dict[str, Any]] # e.g., user message, tool call schema | |
| class StepResult: | |
| observation: Observation | |
| reward: float | |
| done: bool | |
| info: dict[str, Any] = field(default_factory=dict) | |
| class WordleEnv: | |
| """ | |
| Demonstration env. Not a full game; 4-letter variant for brevity. | |
| Observations are emoji strings; actions are 4-letter lowercase words. | |
| Reward is 1.0 on success, else 0.0. Terminal on success or after 6 guesses. | |
| """ | |
| def __init__(self, *, secret: str = "word", max_guesses: int = 6) -> None: | |
| assert len(secret) == 4 and secret.isalpha() | |
| self._secret = secret | |
| self._max = max_guesses | |
| self._n = 0 | |
| self._obs = "β¬" * 4 | |
| def reset(self) -> Observation: # noqa: ARG002 | |
| self._n = 0 | |
| self._obs = "β¬" * 4 | |
| return self._obs | |
| def step(self, action: Action) -> StepResult: | |
| guess: str = str(action) | |
| guess = guess.strip().lower() | |
| if len(guess) != 4 or not guess.isalpha(): | |
| return StepResult(self._obs, -0.05, False, {"error": "invalid guess"}) | |
| self._n += 1 | |
| secret = self._secret | |
| feedback: list[str] = [] | |
| for i, ch in enumerate(guess): | |
| if ch == secret[i]: | |
| feedback.append("π©") | |
| elif ch in secret: | |
| feedback.append("π¨") | |
| else: | |
| feedback.append("β¬") | |
| self._obs = "".join(feedback) | |
| done = guess == secret or self._n >= self._max | |
| reward = 1.0 if guess == secret else 0.0 | |
| return StepResult(self._obs, reward, done, {"guesses": self._n}) | |
| def render(self) -> str: | |
| return self._obs | |
| class SessionContext: | |
| """Session context with typed dependencies.""" | |
| wordle: WordleEnv | |
| async def session_lifespan(server: FastMCP) -> AsyncIterator[SessionContext]: | |
| """Manage session lifecycle with type-safe context.""" | |
| # Initialize on session initialization | |
| wordle = WordleEnv(secret="word") | |
| # try-finally if you need cleanup on session termination | |
| yield SessionContext(wordle=wordle) | |
| # Stateful server (maintains session state) | |
| mcp = FastMCP("StatefulServer", lifespan=session_lifespan) | |
| def step_fn(guess: str, ctx: Context[ServerSession, SessionContext]) -> tuple[str, float, bool, dict]: | |
| """ | |
| Perform a step in the Wordle environment. | |
| Args: | |
| guess (str): The guessed word (4-letter lowercase string). | |
| Returns: | |
| tuple[str, float, bool, dict]: A tuple containing: | |
| - observation: The observation after the step . | |
| - reward: The reward obtained from the step. | |
| - done: Whether the game is done. | |
| - info: Additional info. | |
| """ | |
| wordle = ctx.request_context.lifespan_context.wordle | |
| result = wordle.step(guess) | |
| return result.observation, result.reward, result.done, result.info | |
| # Return an instance of the StreamableHTTP server app | |
| app = mcp.streamable_http_app() | |
| # Run server with streamable_http transport | |
| if __name__ == "__main__": | |
| mcp.run(transport="streamable-http") | |