Skip to main content
Glama

ro-mcp

An MCP server that drives a real Ragnarok Online client session against a local rAthena server, so an agent can log in, walk around, talk to NPCs and fight — and therefore test server content end to end instead of reading log files.

The client is OpenKore, driven through a small control plugin (openkore-plugin/mcpbridge.pl) that exposes a line-oriented JSON socket.

  Claude ⇄ MCP server (stdio)
         ⇄ JSON over TCP :24390
         OpenKore + mcpbridge plugin
         ⇄ RO protocol (plaintext)
         rAthena  login:6900  char:6121  map:5121

Why OpenKore rather than a hand-rolled client

OpenKore ships an exact match for the packet version this server speaks — RagexeRE_2021_11_03 exists in its Send and Receive trees plus a matching recvpackets table — and it already handles pathfinding, reconnects, map changes and inventory. Reimplementing that was the alternative; reusing it was not close.

Packet obfuscation is a non-issue here: rAthena resolves PACKETVER > 20180307 to zero obfuscation keys, which makes the XOR an identity operation. The server confirms this at boot with Packet Obfuscation: Enabled. Keys: 0x00000000, 0x00000000, 0x00000000.

Related MCP server: Roblox Executor MCP Server

Requirements

Requirement

Notes

Perl 5.34+

macOS system Perl is fine

GNU readline

brew install readline — OpenKore's XSTools links against it

Xcode Command Line Tools

Perl's C headers live inside the SDK on modern macOS

Python 3

only to run OpenKore's bundled SCons

Node.js 18+

the MCP server itself

Setup

./setup.sh          # clone OpenKore, patch, build XSTools, install config + plugin

setup.sh is idempotent. It does four things the stock OpenKore build cannot do unattended on Apple Silicon:

  1. Points SConstruct at the /opt/homebrew readline prefix (it only knows the Intel /usr/local one).

  2. Points it at Perl's headers inside the macOS SDK, since Config.pm reports a bare /System/Library path that no longer holds them.

  3. Shims python → python3 for the bundled SCons.

  4. Installs our server entry, config overrides and the mcpbridge plugin — including adding mcpbridge to loadPlugins_list, without which the control socket never opens.

Server-side prerequisites

The rAthena instance needs three settings for unattended login:

File

Setting

Why

conf/login_athena.conf

new_account: yes

lets name_M auto-create an account on first login

conf/char_athena.conf

pincode_enabled: no

the PIN prompt blocks OpenKore's main loop, which stalls the control socket

conf/char_athena.conf

char_new: yes

character creation

The bridge protocol

Newline-delimited JSON, both directions, on 127.0.0.1:24390 (loopback only — this grants full control of a game session).

// requests
{"id": 1, "op": "ping"}
{"id": 2, "op": "state"}
{"id": 3, "op": "run", "cmd": "move 155 180"}

// responses
{"id": 1, "ok": true, "pong": 1}
{"id": 3, "ok": false, "error": "unknown or rejected command: ..."}

// events, pushed as they happen
{"event": "npc_talk", "name": "TS Lab", "msg": "Welcome to the V8 smoke test."}
{"event": "npc_talk_responses", "responses": ["Mob spawn", "Reputation", "Cancel"]}
{"event": "map_changed", "map": "prontera"}

op: run forwards to OpenKore's own command layer rather than reimplementing movement or NPC logic — OpenKore already knows how to path, retry and recover, and duplicating that here would only rot.

Usage

npm install && npm run build
node scripts/smoke.mjs      # end-to-end check against a running server

Register with an MCP client by pointing it at dist/index.js:

{ "mcpServers": { "ro": { "command": "node", "args": ["/path/to/ro-mcp/dist/index.js"] } } }

RO_MCP_KORE_DIR and RO_MCP_PORT override the OpenKore location and bridge port.

Tools

Tool

Purpose

ro_start / ro_stop

launch / shut down the session

ro_state

map, position, HP/SP, level, zeny, nearby NPCs and monsters

ro_prompt / ro_answer

inspect and answer OpenKore's interactive questions

ro_create_char / ro_select_char

character creation and selection

ro_walk / ro_warp

move within a map / travel to another map

ro_talk_npc

talk to an NPC, returning its dialog text and menu options

ro_menu_select / ro_dialog_next / ro_close_dialog

drive a conversation

ro_attack

attack a nearby monster

ro_console / ro_command

read console output / escape hatch

A note on prompts

OpenKore asks some questions on stdin — character creation, character selection, password retry. Those are blocking reads inside its main loop, so while one is pending the loop is stopped and the control socket cannot answer. The MCP server therefore owns the child process's stdin as well as the socket; ro_prompt surfaces a pending question and ro_answer replies to it. If a tool ever times out, check ro_prompt first.

Status

Verified end to end against a live server (scripts/smoke.mjs):

  • account auto-creation, login, character creation and selection

  • ro_state reporting map, position, HP/SP, level, zeny and nearby actors

  • ro_walk moving the character and confirming arrival

  • unreachable destinations rejected in ~30ms with the reason, rather than burning the full timeout — OpenKore reports these only on the console, so the arrival poll is raced against that message

  • a full NPC conversation against TypeScript-scripted content (scripts/npc-test.mjs): reading dialog, parsing menu options, choosing a branch and reading the reply

Not yet exercised: ro_attack.

Two async traps worth knowing

Actor lists populate after map entry. A ro_state taken immediately after login legitimately reports no nearby NPCs; poll until they appear.

A dialog's last message arrives as it closes. Scripts commonly answer and then close in one go, so treat npc_talk_done as "mark closed", never as "discard the buffer" — otherwise the end of a conversation, usually the part being asserted on, is exactly the part lost.

Available Tools

17 tools
ro_answerA

Answer OpenKore's pending interactive question by writing a line to its stdin.

Inputs: answer (required).

ro_attackB

Attack a nearby monster by its binID from ro_state.nearbyMonsters.

Inputs: binID (required), waitMs.

ro_close_dialogB

Close the currently open NPC dialog.

Inputs: no named parameters.

ro_commandA

Escape hatch: run an arbitrary OpenKore console command. Prefer the specific tools; use this for anything they do not cover.

Inputs: cmd (required).

ro_consoleA

Read recent OpenKore console output. Useful when something did not behave as expected.

Inputs: lines.

ro_create_charB

Create a character in the given slot. Only valid while OpenKore is asking for character properties — call ro_prompt first to confirm.

Inputs: name (required), job, sex, slot, +2 more.

ro_dialog_nextC

Advance an NPC dialog that is waiting on a 'next' click.

Inputs: timeoutMs.

ro_gm_warpA

Teleport instantly to a map using the @warp GM command. Requires the account to have a GM group with @warp permission. Unlike ro_warp this needs no walkable route, which matters because some maps (the tutorial grounds, instances) have no path out.

Inputs: map (required), x, y, timeoutMs.

ro_menu_selectB

Choose an option from the NPC's menu, by zero-based index.

Inputs: index (required), timeoutMs.

ro_promptA

Check whether OpenKore is blocked on an interactive question (character creation, character selection, password retry). While a prompt is pending its main loop is stopped, so every other tool will time out until it is answered with ro_answer.

Inputs: no named parameters.

ro_select_charC

Select a character by its slot number and enter the world.

Inputs: slot.

ro_startA

Launch OpenKore and connect to the local rAthena server. Must be called before any other tool. Returns the resulting game state, or the pending prompt if login needs an answer.

Inputs: no named parameters.

ro_stateA

Read the live game state: connection, map, character stats and position, and nearby NPCs/monsters.

Inputs: no named parameters.

ro_stopB

Shut down the OpenKore session.

Inputs: no named parameters.

ro_talk_npcB

Talk to an NPC and return its dialog text plus any menu options. Identify the NPC either by its binID from ro_state, or by map coordinates.

Inputs: x, y, binID, timeoutMs.

ro_walkC

Walk to coordinates on the current map and wait until the character arrives.

Inputs: x (required), y (required), timeoutMs, tolerance.

ro_warpC

Move to another map by name (uses OpenKore's route planner; the path must exist).

Inputs: map (required), x, y, timeoutMs.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 17 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes (start/stop/state/console, character creation/selection, movement, dialog steps, attack). Minor overlap exists between movement tools (ro_walk vs ro_warp vs ro_gm_warp) and dialog tools (ro_talk_npc, ro_menu_select, ro_dialog_next, ro_close_dialog), but the descriptions clarify when each is appropriate.

    Naming Consistency5/5

    All tools follow the `ro_` prefix with a consistent snake_case verb_noun pattern (e.g., ro_create_char, ro_menu_select, ro_gm_warp). Variations like ro_close_dialog or ro_dialog_next still adhere to the same convention, making the naming predictable and easy to infer.

    Tool Count4/5

    At 17 tools, the server is on the higher end but appropriate for a game automation bot that needs to cover startup, character management, movement, NPC interaction, and combat. The count feels justified given the domain, with only slight overlap (e.g., two warp tools) that could be consolidated.

    Completeness4/5

    The toolset covers the core lifecycle (start/stop/state), character setup, movement, NPC dialogs, and basic combat, with an escape hatch (ro_command) for anything else. Minor gaps exist (e.g., inventory or skill handling) but these are outside the apparent scope and can be addressed via the command tool.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers