Skip to main content
Glama
deanxizian

balatro-codex-mcp

by deanxizian

balatro-codex-mcp

balatro-codex-mcp 让 Codex 桌面端通过本地 STDIO MCP 工具控制 Steam 版 《Balatro / 小丑牌》。Python Server 只暴露经过约束的正常游戏动作,不向 Codex 提供任意 JSON-RPC、调试、作弊、存档读写、截图或返回菜单能力。

项目基于:

2026-09-06 升级验证

  • MCP SDK 和 mcp-types 已升级并锁定为 2.1.1,保留官方 MCPServer 与 STDIO 入口。

  • macOS 已验证 Steamodded 26.829.0 与 BalatroBot v1.5.2、Lovely 0.9.0 的启动及只读连接兼容性。

  • 验证涵盖 healthrpc.discovergamestate 和 MCP tools/list;升级验收未执行 游戏动作,完整对局及无尽模式仍需实际游玩验证。

  • 启动脚本将 localhost127.0.0.1::1 加入启动进程的 NO_PROXY / no_proxy, 保留已有绕过规则,避免 macOS 系统代理向健康检查返回 HTML 而触发 JSONDecodeError。 此设置不修改系统代理或 BalatroBot 上游源码。

  • 升级 Steamodded 前应将旧目录备份到 Mods 以外的位置,避免同时加载两份 Mod。 本机升级备份位于 ~/Library/Application Support/balatro-codex-mcp/backups/

  • BalatroBot 保留稳定版。无尽模式弹窗修复 PR #200 目前只在 dev;该分支还要求专用配置档并自动全解锁,本项目不默认切换。

架构

Codex 桌面端
  ↓ STDIO MCP(唯一游戏控制入口)
本项目的 Python MCP Server
  ↓ HTTP JSON-RPC 2.0(默认 http://127.0.0.1:12346)
BalatroBot
  ↓ 游戏内 Mod API
Steam 版 Balatro

MCP Server 和 BalatroBot 使用独立生命周期。Codex 启动 MCP Server 时,Server 会尽力读取 rpc.discover,但绝不会启动、关闭、重置游戏或加载存档;即使 BalatroBot 离线,MCP 初始化和 tools/list 仍会成功,balatro_health 会返回 可执行的修复命令。

为什么 Codex 桌面端使用 MCP

MCP 把参数类型、工具说明、只读/写入提示和 server-level instructions 一起交给 Codex。模型调用 balatro_play_cards(cards=[...], expected_state_token="..."), 而不是通过 Shell 拼接 BalatroBot JSON。这样可以集中执行:

  • 0-based 索引和完整排列校验

  • state_token 乐观并发保护

  • 写锁内二次状态校验

  • 写操作禁止自动重试

  • 工具白名单和危险接口隔离

  • stdout 仅承载 MCP 协议,诊断日志只写 stderr

Balatro、Lovely、Steamodded 与 BalatroBot

  • Balatro 是 Steam 游戏本体。

  • Lovely Injector 把 Mod 加载能力注入 Balatro 的 LÖVE 运行时。

  • Steamodded 是 Balatro 的 Mod loader/API。

  • BalatroBot 是运行在游戏内的 Mod,并在本机提供 HTTP JSON-RPC 2.0。

  • 本项目 不重写或修改 BalatroBot,只把它的正常游戏 API 收窄为 Codex MCP 工具。

安装 Mod 时遵循 BalatroBot 安装文档Lovely 文档Steamodded 文档。本项目脚本不会复制、 删除或修改游戏、Mod 和存档文件。

前置条件

  • macOS 与 Steam 版 Balatro 1.0.1+

  • Lovely 0.8.0+

  • Steamodded 1.0.0-beta-1221a+

  • BalatroBot Mod v1.5.2

  • Python 3.13+(项目 .python-version 固定 3.13 系列)

  • uv

  • Codex 桌面端;建议同时有 Codex CLI

BalatroBot 当前常见路径:

游戏:
~/Library/Application Support/Steam/steamapps/common/Balatro/Balatro.app

Lovely:
~/Library/Application Support/Steam/steamapps/common/Balatro/liblovely.dylib

Mods:
~/Library/Application Support/Balatro/Mods

BalatroBot:
~/Library/Application Support/Balatro/Mods/balatrobot

Quick Start

在项目根目录:

uv sync
./scripts/doctor_macos.sh
./scripts/start_balatro_macos.sh --fast

启动脚本在前台运行 BalatroBot v1.5.2,默认显示游戏窗口。不要关闭这个终端; Ctrl+C 会直接转发给 BalatroBot。--fast 可省略,也可以追加任何 balatrobot serve 参数,例如 --no-shaders。脚本不会后台运行、不会杀死占用端口 的进程,也不会重置游戏或存档。

打开另一个终端:

./scripts/verify_connection.sh
./scripts/configure_codex_mcp.sh

然后:

  1. 重启 Codex 桌面端。

  2. 打开本项目。

  3. 在对话框输入 /mcp

  4. 确认 balatro 已连接。

  5. 输入下文“推荐游戏提示词”。

如果 doctor_macos.sh 报告 Mod 或游戏缺失,先按上游文档补齐;该脚本是只读检查, 不会自动安装或修改系统。

安装 Python 项目

uv sync

该命令创建项目私有环境并生成/使用 uv.lock。完成后,真正交给 Codex 的入口是:

<仓库绝对路径>/.venv/bin/balatro-codex-mcp

可以直接验证入口存在:

test -x .venv/bin/balatro-codex-mcp

不用 uv run 启动 Codex MCP 的原因是:macOS GUI 进程未必继承 Homebrew 或用户 Shell 的 PATH.venv/bin 中的绝对控制台入口已经包含正确的 Python 环境, Codex 无需找到 uvpython3 或 Homebrew。

macOS 检查与启动 BalatroBot

只读检查:

./scripts/doctor_macos.sh

它检查 macOS、uv、Python 3.13+、Steam Balatro、Lovely、Steamodded、BalatroBot Mod、12346 端口、BalatroBot health、Codex CLI、项目 .venv 和绝对 MCP 入口。

前台启动:

./scripts/start_balatro_macos.sh

快速模式:

./scripts/start_balatro_macos.sh --fast

额外参数原样传给上游:

./scripts/start_balatro_macos.sh --fast --no-shaders --fps-cap 60

脚本使用上游推荐的 uvx balatrobot serve 路径并明确固定 v1.5.2。在 macOS, BalatroBot CLI 直接执行:

~/Library/Application Support/Steam/steamapps/common/Balatro/
Balatro.app/Contents/MacOS/love

同时通过 DYLD_INSERT_LIBRARIES 加载 liblovely.dylib。这是 BalatroBot 当前 macOS launcher 的实现;不是鼠标、OCR、截图或 UI 自动化。

只读验证 health

BalatroBot 启动后,在第二个终端运行:

./scripts/verify_connection.sh

该脚本严格只调用:

  • health

  • rpc.discover

  • gamestate

它不会调用 startplaydiscardbuysellrerollpacknext_round 或任何其他写操作。

配置 Codex 桌面端 MCP

自动配置:

./scripts/configure_codex_mcp.sh

脚本先确认 .venv 和绝对入口存在,然后执行等价于:

codex mcp add balatro \
  --env BALATROBOT_URL=http://127.0.0.1:12346 \
  -- /ABSOLUTE/PATH/TO/balatro-codex-mcp/.venv/bin/balatro-codex-mcp

如果 balatro 已存在,脚本会停止并要求先检查/移除旧配置,不会静默覆盖。成功后 会运行:

codex mcp list

也可以在 Codex 桌面端手动添加:

Settings → MCP servers → Add server → STDIO
Name: balatro
Command: <仓库绝对路径>/.venv/bin/balatro-codex-mcp
Environment: BALATROBOT_URL=http://127.0.0.1:12346

保存后必须重启 Codex 桌面端。重启后在对话框输入:

/mcp

确认 balatro 状态为 connected。可参考 .codex/config.toml.example;示例只含占位路径, 不包含开发机用户名。

环境变量

变量

默认值

说明

BALATROBOT_URL

http://127.0.0.1:12346

BalatroBot JSON-RPC 根地址

BALATROBOT_TIMEOUT

15

单次 HTTP 超时秒数

BALATROBOT_ALLOW_NON_LOOPBACK

0

仅显式设为 1 才允许非回环地址

BALATROBOT_READ_RETRIES

1

只读连接级重试次数,范围 0–3

默认只接受 localhost127.0.0.1::1。URL 不允许凭据、额外路径、query 或 fragment。非回环模式会扩大信任边界,不建议用于普通本地游戏。

MCP 工具

所有工具都有 balatro_ 前缀。

只读工具

工具

用途

balatro_health()

MCP/BalatroBot 状态、health、版本、当前阶段、discover 结果、缺失能力和 macOS 修复命令

balatro_capabilities(refresh=false)

安全上游方法的精简 OpenRPC 参数与 required states;可强制刷新缓存

balatro_get_state(view="decision")

读取紧凑 decision 或保留全部未知字段的 full state

balatro_available_actions()

当前允许/不允许动作、原因、参数、有效索引范围与 token

写工具

每个写工具都强制要求 expected_state_token,不提供可选默认值。

工具

上游方法

主要约束

balatro_start_run(deck, stake, expected_state_token, seed=None)

start

MENU

balatro_select_blind(expected_state_token)

select

BLIND_SELECT

balatro_skip_blind(expected_state_token)

skip

Boss Blind 禁止跳过

balatro_play_cards(cards, expected_state_token)

play

1–5 张、唯一、有效手牌索引

balatro_discard_cards(cards, expected_state_token)

discard

检查剩余弃牌和高亮上限

balatro_cash_out(expected_state_token)

cash_out

ROUND_EVAL

balatro_buy_shop_item(kind, index, expected_state_token)

buy

kind 转为 card/voucher/pack 参数

balatro_reroll_shop(expected_state_token)

reroll

SHOP,检查明显资金不足

balatro_sell_item(kind, index, expected_state_token)

sell

joker/consumable,拒绝 Eternal

balatro_use_consumable(consumable_index, target_cards, expected_state_token)

use

不猜测目标数量

balatro_choose_pack_item(item_index, target_cards, expected_state_token)

pack

转为 {card, targets}

balatro_skip_pack(expected_state_token)

pack

转为 {skip: true}

balatro_rearrange(area, order, expected_state_token)

rearrange

order 必须是完整排列

balatro_next_round(expected_state_token)

next_round

SHOP

所有写操作统一执行:

  1. 读取最新 gamestate

  2. 计算并比较 token。

  3. 校验阶段、参数、资源和索引。

  4. 获取唯一写锁。

  5. 在锁内再次读取状态并校验 token。

  6. 向 BalatroBot 发送恰好一次写 RPC。

  7. 再次读取状态;如果该只读请求失败,使用写响应中的 gamestate 作为明确降级。

  8. 返回上游结果、旧/新 token、完整新 decision state、warnings,并写动作日志。

decision state、state_token 与 0-based 索引

balatro_get_state(view="decision") 保留当前阶段、seed、deck、stake、ante、round、 Blind 目标分数/奖励(上游提供时)/特殊效果、分数、钱、bankrupt_at(上游提供时)、 剩余手数/弃牌数、胜负、手牌、Joker、消耗牌、voucher、商店、补充包、reroll 费用、槽位上限、可用动作、warnings 和 state_token

每张卡或物品都保留原始 BalatroBot 字段,并额外提供:

  • 当前 index

  • balatrobot_idkeylabel/name

  • description

  • ranksuit

  • enhancementeditionsealdebuff

  • buy_costsell_value

所有索引均为 0-based。任何状态改变后,手牌、商店、Joker、消耗牌和 Pack 的 旧索引立即失效。

state_token 使用 action-relevant 状态的规范化 JSON、稳定 key 排序和 SHA-256 生成。同一状态重复读取会得到相同 token;阶段、主要资源、手牌顺序、商店、 Joker、消耗牌或 Pack 改变都会换 token。

如果传入旧 token,工具返回 STALE_STATE、实际 token 和最新 decision state, 且不会执行上游写操作。正确处理方式是重新决策,不是盲目重试原动作。

开始或继续一局

推荐把下面整段交给 Codex:

请用 balatro MCP 玩小丑牌。先调用 balatro_health,再读取
balatro_get_state(view="decision")。如果已有一局,继续当前局,不要返回菜单,
不要开始新局覆盖它;如果状态是 MENU,则用 RED deck、WHITE stake 开始一局。
每次动作前都重新读取最新 decision state,只使用 available_actions 中允许的动作,
所有索引按 0-based,并把最新 state_token 作为 expected_state_token。每个写操作后
检查 new_state。不要盲目重试失败工具,不要跳过 Boss Blind;商店操作前检查钱和
槽位。进入 GAME_OVER 后停止操作,汇总 ante、round、胜负和关键 Joker。

如果只想继续、不允许开始新局,把其中 MENU 分支改为“若为 MENU,只报告当前没有 进行中的局,不要调用 balatro_start_run”。

常见错误

错误/现象

含义与处理

CONNECTION_FAILED

在独立终端运行 ./scripts/doctor_macos.sh,再运行 ./scripts/start_balatro_macos.sh --fast

启动时 JSONDecodeError

若本机 HTTP 请求被系统代理返回 HTML,使用已加入本机地址 NO_PROXY 的启动脚本。其他非 JSON 响应仍须按日志排查,不能靠重复游戏动作修复

UPSTREAM_TIMEOUT

确认游戏未卡死;动作结果未知时先读状态,绝不能重复写操作

STALE_STATE

使用错误附带的 latest_state 重新决策

INVALID_GAME_STATE

当前界面不允许该动作;读 balatro_available_actions

INDEX_OUT_OF_RANGE

状态已变化或索引错误;重新读取 decision state

DUPLICATE_INDEX

卡牌索引或排列有重复

INCOMPLETE_PERMUTATION

rearrange.order 必须包含区域的全部索引且只出现一次

INSUFFICIENT_MONEY / NO_FREE_SLOT

先卖出、选择其他物品或离开商店

BOSS_BLIND_CANNOT_BE_SKIPPED

必须选择并打 Boss Blind

12346 被占用

启动脚本会报出监听进程但不会杀进程;确认是否已有 BalatroBot

balatro 配置已存在

codex mcp get balatro 检查;确认后用移除脚本,再重新添加

/mcp 看不到新配置

完整重启 Codex 桌面端,并确认配置使用 .venv/bin 的绝对入口

更新 BalatroBot 后验证接口

  1. 阅读新 release 的 pyproject.tomlsrc/lua/utils/openrpc.jsonsrc/lua/utils/gamestate.luasrc/lua/endpoints/*.lua

  2. 更新 scripts/start_balatro_macos.sh 中的固定版本。

  3. 启动新版本后运行只读验证:

    ./scripts/verify_connection.sh
  4. 在 Codex 中调用:

    balatro_capabilities(refresh=true)
  5. 运行完整项目验收:

    uv sync
    uv run ruff check .
    uv run ruff format --check .
    uv run ty check
    uv run pytest

如果上游方法参数或状态发生变化,更新兼容表、action guard、工具说明、README 和 tools/list 测试后再允许游戏写操作。

当前上游差异与已知限制

实施时核对了 v1.5.2 和 main,而不是依赖旧教程。发现:

  1. BalatroBot package/release 为 v1.5.2,但 v1.5.2 内嵌 OpenRPC info.version 仍为 1.5.1;main 的相同文件写为 1.5.2。health 中的版本因此 明确标注来源为 rpc.discover.info.version

  2. 当前 rpc.discover 没有发布 endpoint 的 requires_state。项目以 v1.5.2 src/lua/endpoints/*.lua 的实际约束补齐,并在 capabilities 中标注来源。

  3. OpenRPC 的 State enum 没列出 SMODS_BOOSTER_OPENED,但实际 pack.lua 明确要求该状态;项目按运行中的实际 endpoint 名称处理。 同时,v1.5.1/v1.5.2 的 rpc.discover 漏列了实际已注册的 pack endpoint; 项目仅对这两个已核实版本使用 endpoint 兼容表,并在 capabilities 的 compatible_openrpc_omissions 中明确报告,不把它误报为缺失能力。

  4. v1.5.2 gamestate 没有显式暴露 bankrupt_at 或 Blind 奖励。decision state 在上游缺失时返回 null,不伪造数据;购买的最终合法性由 BalatroBot 决定。

  5. 消耗牌目标数量不是当前 OpenRPC 参数 schema 的一部分。项目只校验类型、唯一性 和索引范围,具体数量遵从描述和 BalatroBot 错误。

  6. view="full" 保留上游未知字段;decision 是面向决策的非破坏性字段重组,但不 承诺包含与动作无关的未来顶层字段。

  7. 这是控制接口,不负责安装 Mod,不使用 OCR、截图识别或鼠标坐标,也不在开发 验收中自动开始真实游戏。

日志

  • 动作日志:logs/actions.jsonl

  • 轮转:单文件约 2 MB,保留 3 个备份

  • BalatroBot 自身日志:上游默认 logs/<timestamp>/<port>.log

  • MCP 普通日志:stderr

  • MCP stdout:只含协议数据

动作日志只记录动作名、上游方法、参数形状/索引摘要、结果与 token;不会写环境变量、 完整状态或 seed 值。

移除 Codex MCP 配置

./scripts/remove_codex_mcp.sh

脚本会先显示即将执行的 codex mcp remove balatro,并且只移除名为 balatro 的 Codex MCP 配置。它不会删除项目、虚拟环境、BalatroBot Mod 或游戏存档。

安全边界

本项目绝不注册或转发以下 BalatroBot 能力:

  • add

  • set

  • load

  • save

  • screenshot

  • menu

同样不存在 execute_rawcall_method、任意 JSON-RPC、debugeval、改钱、 改分、生成 Joker、跳 Ante、强制胜利或加载 checkpoint 工具。这些名称不会出现在 tools/list

其他边界:

  • MCP 是 Codex 唯一的游戏控制入口。

  • 写 RPC 自动重试次数永远为 0。

  • 所有写操作共用一个 asyncio.Lock

  • 只读连接失败可以有限重试。

  • HTTP 客户端是单个可复用的 httpx.AsyncClient

  • 进程退出时关闭 HTTP client 和动作日志,不遗留阻止退出的后台任务。

  • 不修改 BalatroBot 上游源码。

  • 不修改 Balatro 存档。

  • 返回主菜单只能由用户直接在游戏界面操作。

  • GAME_OVER 后 instructions 要求停止动作并总结。

开发与测试

默认测试全部使用 httpx.MockTransport,不需要 Balatro 或 BalatroBot:

uv sync
uv run ruff check .
uv run ruff format --check .
uv run ty check
uv run pytest

测试覆盖连接拒绝、超时、HTTP/JSON/JSON-RPC 错误、ID 不匹配、discover 缓存、 unknown fields、decision view、稳定 token、stale 拒绝、索引与排列、参数转换、 写操作零重试、并发写串行化、危险工具缺失,以及 BalatroBot 离线时的真实 STDIO MCP 初始化和 tools/list