Spaces:
Sleeping
Sleeping
File size: 4,522 Bytes
bea46e9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
import os
import random
from typing import Dict, List, Optional
import chess
import chess.pgn
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(name="ChessServer", stateless_http=True)
# Simple in-memory game state keyed by session_id
games: Dict[str, Dict] = {}
def _get_session_id() -> str:
# Provide a default single-session id when not given by client
return "default"
def _ensure_game(session_id: str) -> Dict:
if session_id not in games:
games[session_id] = {
"board": chess.Board(),
"pgn_game": chess.pgn.Game(),
"node": None,
}
games[session_id]["node"] = games[session_id]["pgn_game"]
return games[session_id]
@mcp.tool(description="Start a new chess game. Returns initial board state.")
def start_game(session_id: Optional[str] = None, player_color: str = "white") -> Dict:
sid = session_id or _get_session_id()
game = {
"board": chess.Board(),
"pgn_game": chess.pgn.Game(),
"node": None,
}
if player_color.lower() not in ("white", "black"):
player_color = "white"
# If AI should play first (player is black), make an AI opening move
if player_color.lower() == "black":
# Let engine choose a random legal move
ai_move = random.choice(list(game["board"].legal_moves))
game["board"].push(ai_move)
game["node"] = game["pgn_game"]
games[sid] = game
return _board_state(game["board"]) | {"session_id": sid, "player_color": player_color}
def _board_state(board: chess.Board) -> Dict:
return {
"fen": board.fen(),
"unicode": board.unicode(borders=True),
"turn": "white" if board.turn else "black",
"is_game_over": board.is_game_over(),
"result": board.result() if board.is_game_over() else None,
"legal_moves": [board.san(m) for m in board.legal_moves],
}
@mcp.tool(description="Make a player move in SAN or UCI notation.")
def player_move(move: str, session_id: Optional[str] = None) -> Dict:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
board: chess.Board = g["board"]
try:
try:
chess_move = board.parse_san(move)
except ValueError:
chess_move = chess.Move.from_uci(move)
if chess_move not in board.legal_moves:
raise ValueError("Illegal move")
board.push(chess_move)
# Update PGN
g["node"] = g["node"].add_variation(chess_move)
return _board_state(board) | {"last_move": board.san(chess_move)}
except Exception as e:
return {"error": f"Invalid move: {e}"}
@mcp.tool(description="Have the AI make a move (random legal move).")
def ai_move(session_id: Optional[str] = None) -> Dict:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
board: chess.Board = g["board"]
if board.is_game_over():
return _board_state(board)
move = random.choice(list(board.legal_moves))
board.push(move)
g["node"] = g["node"].add_variation(move)
return _board_state(board) | {"last_move": board.san(move)}
@mcp.tool(description="Return current board state.")
def board(session_id: Optional[str] = None) -> Dict:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
return _board_state(g["board"]) | {"session_id": sid}
@mcp.tool(description="List legal moves in SAN notation.")
def legal_moves(session_id: Optional[str] = None) -> List[str]:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
b: chess.Board = g["board"]
return [b.san(m) for m in b.legal_moves]
@mcp.tool(description="Game status including check, checkmate, stalemate.")
def status(session_id: Optional[str] = None) -> Dict:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
b: chess.Board = g["board"]
return {
"turn": "white" if b.turn else "black",
"is_check": b.is_check(),
"is_checkmate": b.is_checkmate(),
"is_stalemate": b.is_stalemate(),
"is_insufficient_material": b.is_insufficient_material(),
"is_game_over": b.is_game_over(),
"result": b.result() if b.is_game_over() else None,
}
@mcp.tool(description="Export game PGN.")
def pgn(session_id: Optional[str] = None) -> str:
sid = session_id or _get_session_id()
g = _ensure_game(sid)
game = g["pgn_game"]
exporter = chess.pgn.StringExporter(headers=True, variations=False, comments=False)
return game.accept(exporter)
|