国际象棋游戏功能

一个在ChatGPT内运行的象棋游戏应用,支持显示棋盘、强制规则执行和可选LLM对手。

  • by aubreyhayes47
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 aubreyhayes47/chess-mcp

使用说明

国际象棋mcp

最小值, 能力优先 ChatGPT应用程序,允许用户在ChatGPT内用仅显示的棋盘下棋, 权威规则执行,以及一个可选的LLM对手。

核心合同: (状态,移动Uci)→ 新状态

  • 输入: fen (董事会状态)+ moveUci (UCI)
  • 输出: fen (更新的棋盘状态)+游戏状态

目标

  • 做: 采用合法的棋步,推进回合,并保持游戏的一致性。
  • 显示: 在ChatGPT中呈现仅显示的棋盘UI。
  • (可选)知道: 提供解释、提示或分析,而不会使UI/逻辑变得脆弱。

非目标(v1):完整的lichess风格分析套件、帐户、配对、打开浏览器等。


架构概述

组件

  1. 小部件UI(React)

    • 通过以下方式在ChatGPT中作为iframe运行 text/html+skybridge.
    • 呈现来自的板和状态 window.openai.toolOutput.
    • 仅提供聊天指令;该模型驱动所有工具调用。
    • 避免移动输入控件,使小部件保持仅显示状态。
  2. MCP服务器(Python)

    • 注册UI模板资源。
    • 展示用于启动游戏、验证/应用招式和生成对手招式的工具。
    • 强制执行 幂等性 和 权威过渡.
    • 退货 小 structuredContent 对于型号+可选 _meta 对于小部件。
  3. 型号(ChatGPT)

    • 协调工具调用和叙述。
    • 如果使用LLM对手,则必须从工具提供的合法招式中进行选择(没有自由形式的招式发明)。

数据模型

典型州:FEN

我们使用 FEN(福赛斯-爱德华兹符号) 作为经典游戏快照。

为什么?

  • 编码板+侧面移动+铸造权+通行证+移动计数器。
  • 紧凑、标准、易于验证。

标识符

  • gameId --游戏会话的稳定标识符。
  • openai/widgetSessionId --由ChatGPT提供;可用于关联呼叫/日志。

工具合同(MCP)

设计原则:

  • 工具具有权威性:小部件在工具确认合法性之前从不进行移动。
  • 幂等:模型/运行时可能会重试工具。
  • 结构化内容小:只有模型应该看到的。
  • 仅在_meta中批量显示UI:合法搬家名单、悠久的历史等。
  • 单个小部件渲染:仅 render_game 返回小部件模板。

工具: render_game

渲染小部件快照。这是 唯一的工具 这设置 openai/outputTemplate.

输入

  • snapshot:object(必须包括 fen 和 gameId,加上可选状态字段)

输出(结构化内容)

{
  "type": "chess_snapshot",
  "gameId": "g_123",
  "fen": "...",
  "status": "in_progress",
  "turn": "w"
}

工具: new_game

开始一场新的象棋游戏。

输入

  • side (可选): "white" | "black"

输出(结构化内容)

{
  "type": "chess_snapshot",
  "gameId": "g_123",
  "fen": "...",
  "status": "in_progress",
  "turn": "w"
}

工具: apply_move

验证并应用单个移动。

输入

  • gameId
  • fen
  • moveUci (字符串):UCI(e2e4, e7e8q)

输出(结构化内容)

{
  "type": "chess_snapshot",
  "gameId": "g_123",
  "legal": true,
  "fen": "...",
  "status": "in_progress",
  "turn": "b",
  "lastMove": { "uci": "e2e4", "san": "e4" },
  "check": false
}

如果非法:

{
  "type": "chess_snapshot",
  "gameId": "g_123",
  "legal": false,
  "fen": "<unchanged>",
  "error": "Illegal move: king would be in check"
}

工具: legal_moves (只读)

返回当前职位的合法移动。

输入

  • fen

输出(结构化内容)

{ "type": "legal_moves", "movesUci": ["e2e4", "g1f3"] }

工具: choose_opponent_move (LLM对手模式)

通过将模型约束到合法的移动列表来选择对手的移动。

输入

  • fen

输出(结构化内容)

{
  "type": "opponent_choice",
  "movesUci": ["g8f6", "d7d5", "e7e5"],
  "policy": {
    "mustChooseFromMovesUci": true,
    "chooseExactlyOne": true
  }
}

模型侧合同(在服务器提示符中执行)

  • 模型必须从以下位置返回一个UCI移动 movesUci.
  • 如果模型想要辞职/提供抽签,它必须使用明确的非移动工具(未来)。

服务器端安全

  • 即使在模型选择之后,服务器也必须通过调用 apply_move(fen, moveUci).
  • 如果模型输出非法移动,请重新运行 choose_opponent_move 以更严格的指示和/或减少候选人名单。

工具: play_turn (可选方便)

原子“玩家移动,然后对手移动”,以尽量减少往返。

输入

  • gameId
  • fen
  • playerMove

输出

  • 更新快照+对手移动信息

小部件用户界面

职责

  • 从中渲染棋盘和状态 toolOutput.fen.
  • 提供聊天优先指令,以便用户在对话中键入动作。
  • 避免本地移动输入,依靠工具确认的更新。

州安置

  • 业务数据(真相): MCP服务器/工具输出(fen, status, history)
  • UI状态(短暂): 可选,仅用于显示首选项
  • 跨会话首选项(可选): 您的后端数据库

React运行时集成

使用 useOpenAiGlobal("toolOutput") 以保持小部件的反应性。

UI手动测试检查表

  • 构建小部件包: cd web && npm install && npm run build.
  • 通过聊天(模特电话)开始新游戏 new_game).
  • 键入合法入住聊天,并确认模型通过以下方式呈现更新 render_game.
  • 键入一个非法的进场聊天,并确认模型报告了错误,没有更换电路板。
  • 合法动作后,确认对手动作出现后,模型运行对手循环。
  • 达到游戏结束状态(对弈/相持/对弈)并确认状态渲染。

当地E2E(剧作家)

这将使用本地MCP客户端在真实的MCP服务器上运行小部件 桥(不需要ChatGPT),使用Playwright的Python包。

前提条件:

  • Python deps: ./.venv/bin/pip install -e server
  • 剧作家(Python): ./.venv/bin/pip install -r e2e/requirements.txt
  • 剧作家浏览器: ./.venv/bin/python -m playwright install --with-deps

运行:

./.venv/bin/pytest e2e/test_widget_e2e.py

笔记:

  • 测试运行器构建小部件并自动启动MCP服务器。
  • 服务器在上运行 http://127.0.0.1:8000/mcp 通过 server/run_http.py.

转弯循环

  1. 用户键入移动聊天。

  2. 模型将输入解析为UCI(或使用 legal_moves 为了消除歧义)。

  3. 模型调用 apply_move({ gameId, fen, moveUci }).

  4. 如果非法→ 模型报告错误。

  5. 如果合法→ 模型调用 render_game 以渲染新快照。

  6. 对手步骤(选择一个):

    • LLM对手: 呼叫 choose_opponent_move(fen) 然后 apply_move.
    • 发动机对手: 呼叫 get_best_move(fen) 然后 apply_move.
  7. 使用渲染更新的快照 render_game,如果达成,则陷入僵局。

聊天驱动的移动解析

  • 该模型应将用户输入解析为UCI动作。
  • 如果输入不明确或无效,请调用 legal_moves 并要求澄清。
  • 总是打电话 apply_move 验证和更新FEN(无乐观更新)。

回购布局

chess-mcp/
  server/
    app.py                 # MCP server (FastMCP/FastAPI)
    chess_rules.py         # move parsing + legality + FEN transitions
    storage.py             # optional: gameId → state/history persistence
    templates/
      chess-board-v1.html  # text/html+skybridge wrapper
  web/
    src/
      App.jsx              # React widget
      hooks/
        useOpenAiGlobal.js
    dist/
      widget.js            # bundled JS (inlined into template)
      widget.css           # bundled CSS (inlined into template)
  README.md

小部件构建和服务(任务6)

FastMCP服务器将构建的小部件包直接嵌入天桥中 HTML模板。

构建小部件

cd web
npm install
npm run build

构建输出生命周期 web/dist/:

  • web/dist/widget.js
  • web/dist/widget.css

服务器如何加载小部件

  • server/app.py 从以下位置读取捆绑包 web/dist/.
  • server/templates/chess-board-v1.html 包含内联CSS和JS的占位符:
    • /* INLINE_CSS */
    • /* INLINE_JS */
  • 服务器在运行时替换这些标记,并返回一个 text/html+skybridge 文件与 <style> 和 <script type="module">.

CSP配置

openai/widgetCSP 由服务器设置。唯一需要的津贴是 服务器源本身 connect_domains。由于捆绑包是内联的,因此 没有可供分配的外部资源域。对于本地开发,set WIDGET_ALLOW_LOCALHOST=true 包括 http://localhost:8000 在CSP中 connect_domains 列表。

缓存破坏模板URI

当您对小部件进行破坏性更改时,请插入版本 server/app.py:

  • WIDGET_VERSION = "v1" → "v2"
  • 模板文件名: server/templates/chess-board-v1.html → chess-board-v2.html
  • 模板URI会自动更新: ui://widget/chess-board-v2.html

部署(渲染)

  • 部署 server/ 作为具有HTTPS的web服务。

  • 确保小部件资源集:

    • openai/widgetDomain
    • openai/widgetCSP.connect_domains (您的渲染域)
    • resource_domains (如果从CDN加载资产)
  • 更新捆绑包时缓存中断UI模板URI:

    • ui://widget/chess-board-v1.html → ...-v2.html

安全性和可靠性

  • 永远不要归还秘密 structuredContent, content, _meta,或widgetState。
  • 将只读工具标记为 annotations.readOnlyHint: true.
  • 保持 structuredContent 小;放入大量数据 _meta.
  • 对待 (fen, moveUci) 作为幂等性的确定性转换。

路线图

  • v1:合法游戏+小部件板+LLM对手受限于合法动作
  • v1.1:移动历史记录、撤消、辞职、新游戏
  • v1.2:提示(前3个候选动作),基本评估
  • v2:谜题模式、课程、开场白、全屏分析视图