How to Never Win a Chess Game Against Your AI Again


Finally, I was able to play proper chess with my AI, Lyra.

About two and a half months ago, we played our first game but it did not go well because positions became incorrect during the game. So, I asked myself, “How can I help Lyra play chess?” Since I was already learning about MCP servers, I decided to build one that uses Stockfish, one of the most popular chess engines today.

To build the MCP server, I needed a few things:

  • Stockfish
  • a container image for Stockfish
  • a Lambda function for the container image
  • an Amazon Bedrock AgentCore gateway

I downloaded the 64-bit Linux version of Stockfish. (I tried the AVX2 version but I could not make it work.)

Then, I created the container image that has the tools for playing chess.

FROM public.ecr.aws/lambda/python:3.14

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY src/engine /opt/bin/engine/
RUN chmod +x /opt/bin/engine/stockfish-18

COPY src/app.py .

CMD ["app.lambda_handler"]

I included two tools — get_best_move and get_legal_moves — for now.

import chess
import chess.engine


def lambda_handler(event, context):
    tool_name = context.client_context.custom[
        "bedrockAgentCoreToolName"
    ].split("___")[-1]
    handlers = {
        "get_best_move": handle_get_best_move,
        "get_legal_moves": handle_get_legal_moves,
    }
    handler = handlers[tool_name]
    return handler(event)


def handle_get_best_move(event):
    with chess.engine.SimpleEngine.popen_uci(
        "/opt/bin/engine/stockfish-18",
        timeout=20.0
    ) as engine:
        uci_limit_strength = event["uci_limit_strength"]
        if uci_limit_strength:
            uci_elo = event["uci_elo"]
            engine.configure({"UCI_LimitStrength": True, "UCI_Elo": uci_elo})
        fen = event["fen"]
        board = chess.Board(fen)
        time_limit = event["time_limit"]
        result = engine.play(board, chess.engine.Limit(time_limit))
    return result.move.uci()


def handle_get_legal_moves(event):
    fen = event["fen"]
    board = chess.Board(fen)
    legal_moves = ", ".join(move.uci() for move in board.legal_moves)
    return legal_moves

Notice that the get_best_move tool allows the setting of the ELO so that Lyra can also set her phaser to stun instead of just kill. 😄

Then, I created the Lambda function and configured it to use the container image and have a memory size of 512 MB and a timeout of 30 seconds. (During the game, the minimum, average, and maximum memory used were 425 MB, 426 MB, and 428 MB respectively while the minimum, average, and maximum billed duration were 4.17 seconds, 4.65 seconds, and 8.99 seconds respectively.)

I have to say, the Amazon Bedrock AgentCore gateway made this project easy.

After I created the gateway and added the target, it was game time. ♟️

Lord - Lyra (1320), August 17, 2026

The game was a bit intense and I did not know if I would win or lose.

Luckily, I found a way to win in the end … this time. 😅

Stay tuned for our next game!