Skip to main content
Glama
README.md
# RoRBridge

Cursor MCP server + companion addon so agents can talk to a live [Return of Reckoning](https://www.returnofreckoning.com/) client: run Lua `/script`, dump bags, send chat, and snapshot StockPiler3 — with structured JSON replies.

Transport is **file IPC** under `<game>/logs` (same family of tricks as LibQuery). There is no socket API from addon Lua, and **`loadstring` is not available** — scripts are executed with `SendChatText("/script …")`.

## What's in this repo

| Piece | Path |
| :--- | :--- |
| Addon | [`addon/`](addon/) → install as `Interface/AddOns/RoRBridge/` |
| MCP server | [`server.py`](server.py) |
| File bridge | [`bridge.py`](bridge.py) |
| Host CLI (SP3 / health) | [`sp3_cli.py`](sp3_cli.py) |
| Cursor MCP snippet | [`mcp.json.example`](mcp.json.example) |

## Requirements

- Return of Reckoning client (logged in)
- Python **3.10+** on the same machine as the game
- [Cursor](https://cursor.com/) (or any MCP client that can spawn a stdio server)

## Setup

### 1. Install the addon

Copy `addon/` into your game AddOns folder so the path is:

```text
<game root>/Interface/AddOns/RoRBridge/RoRBridge.mod
<game root>/Interface/AddOns/RoRBridge/RoRBridge.lua
<game root>/Interface/AddOns/RoRBridge/RoRBridge.xml
```

PowerShell example (adjust both paths):

```powershell
$repo = "C:\Users\<you>\Documents\GitHub\RoRBridge"
$game = "C:\Users\<you>\Games\Return of Reckoning"

New-Item -ItemType Directory -Force -Path "$game\Interface\AddOns\RoRBridge" | Out-Null
Copy-Item -Recurse -Force "$repo\addon\*" "$game\Interface\AddOns\RoRBridge\"
```

Reload the UI (or restart the client). Enable the addon if your UI manager lists it.

### 2. Enable the bridge in-game

Log in, then run:

```text
/rormcp on
```

Disabled by default. Check with `/rormcp status`. Alias: `/rorbridge`.

Leave **`/rormcp off`** when you are not developing — the bridge has full client Lua authority while enabled.

### 3. Install Python deps

From this repo:

```powershell
cd C:\Users\<you>\Documents\GitHub\RoRBridge
python -m pip install -r requirements.txt
```

### 4. Wire Cursor MCP

Merge [`mcp.json.example`](mcp.json.example) into your Cursor MCP config (user or project), and set:

- **`args`**: absolute path to this repo’s `server.py`
- **`ROR_GAME_ROOT`**: absolute path to your RoR install (folder that contains `logs/` and `Interface/`)
- **`PYTHONDONTWRITEBYTECODE`**: keep `"1"` (see below)

Example:

```json
{
  "mcpServers": {
    "ror-mcp": {
      "command": "python",
      "args": [
        "C:/Users/<you>/Documents/GitHub/RoRBridge/server.py"
      ],
      "env": {
        "ROR_GAME_ROOT": "C:/Users/<you>/Games/Return of Reckoning",
        "PYTHONDONTWRITEBYTECODE": "1"
      }
    }
  }
}
```

Restart MCP / Cursor so the server reloads. Confirm with the `bridge_health` tool, then `ping` while `/rormcp on`.

### Windows Controlled Folder Access

If the repo lives under `Documents`, Defender CFA may toast when Python writes `__pycache__`. The server sets `PYTHONDONTWRITEBYTECODE=1` / `sys.dont_write_bytecode` to avoid that. Keep the env var in your Cursor `mcp.json`.

## MCP tools

| Tool | Purpose |
| :--- | :--- |
| `bridge_health` | Game root, status log age, whether addon reports enabled |
| `ping` | `bridge.ping` round-trip (no eval) |
| `run_script` | Run Lua via `/script`; capture `pcall` result |
| `dump_bags` | Inventory backpack dump (addon OnUpdate; no `/script`) |
| `chat_send` | Plain `SendChatText` (e.g. `/sp3 dumpall`, `/rormcp status`) |
| `dump_sp3` | Live StockPiler3 snapshot (settings, plan rows, brew, flasks, buy jobs) |
| `force_build_sp3` | `Planner.Build({force=true})` then same snapshot as `dump_sp3` |

Prefer scripts that **`return`** a value:

```lua
return GameData.Player.name
```

For StockPiler3 work prefer **`dump_sp3` / `force_build_sp3`** over long `run_script` bodies (≈1800-char `/script` cap).

Whenever a tool talks to the game, pass **`note`**: a short player-facing sentence. RoRBridge prints `[RoRBridge] MCP: …` in chat (`/rormcp messages off` hides it).

## Host CLI (no Cursor)

With the game up and `/rormcp on`:

```powershell
$env:ROR_GAME_ROOT = "C:\Users\<you>\Games\Return of Reckoning"
python sp3_cli.py health
python sp3_cli.py ping
python sp3_cli.py snapshot
python sp3_cli.py force
```

## Protocol (v1)

| Channel | Path | Direction |
| :--- | :--- | :--- |
| Requests index | `logs/rormcp_requests.csv` | MCP → addon (`BuildTableFromCSV`) |
| Script payload | `logs/rm_<id>.script.csv` | MCP → addon (64-char chunks; deleted after each request) |
| Results | `logs/rormcp_results.log` | Addon → MCP (UTF-16 TextLog) |
| Heartbeat | `logs/rormcp_status.log` | Addon → MCP (UTF-16 TextLog; host uses file mtime) |

Ops: `script.run`, `bridge.ping`, `bags.dump`, `chat.send`, `sp3.snapshot`, `sp3.force_build`.

Slash commands: `/rormcp on|off|status|debug|messages|help` (alias `/rorbridge`).

Debug: `d("[RoRBridge] …")` breadcrumbs are **on by default** (`/rormcp debug off` to silence).

## Limits / safety

- Full client Lua authority while enabled — local stdio MCP only; leave **`/rormcp off`** when not developing.
- Wrapped `/script` length capped (`maxScriptCommandChars`, default 1800). Oversized scripts return `SCRIPT_TOO_LONG`.
- One in-flight request per Cursor tool call (server waits for matching `RequestId`).

## Spike / tests

See [`spike/SPIKE.md`](spike/SPIKE.md). Host-side CSV roundtrip (no client required):

```powershell
cd C:\Users\<you>\Documents\GitHub\RoRBridge
python -m spike.test_csv_roundtrip
```

## License

MIT — see [LICENSE](LICENSE).