The GoMMO client can be driven by a program outside it: Claude Code, Claude Desktop, any other Model Context Protocol client, a Python loop, or curl. The program sees what the character sees and acts through the same actions a client script uses: walk, fight, loot, wear, drink, talk.
Nothing listens unless you start the client with -mcp or -serve.
The API arrived in GoMMO Client 0.1.0. A section that arrived later names its version, and -version prints the version you have.
Starting it
The client is GoMMO Client, a download of its own from the GoMMO Client page: GoMMO_Client.exe for Windows, and a Linux build with no window for a server. GoMMO Client covers installing it and pointing it at your game. The examples below assume it is unzipped to C:\GoMMO and plays RF Online from C:\RF Online.
| You want | Run |
|---|---|
| Claude Code to play | claude mcp add gommo -- "C:\GoMMO\GoMMO_Client.exe" -game rf2232 -rf "C:\RF Online" -headless -mcp |
| Your own program over HTTP | GoMMO_Client.exe -game rf2232 -rf "C:\RF Online" -headless -serve |
| To watch while it plays | leave out -headless: the window opens and the agent drives the character in it |
-headless needs no graphics driver, which is what makes the Linux build useful on a server.
-game picks the game, and the game folder comes with it: -rf <folder> for RF Online, -wokf <folder> for World of Kung Fu, or -data <folder>/data for Gate to Heavens. The client reads monster and item names from that folder. -host and -port pick the server.
The client logs in with the account in the [Account] section of gommo.ini, which it makes on its first run. -login and -password name another.
Claude Desktop
Add the client to claude_desktop_config.json:
{
"mcpServers": {
"gommo": {
"command": "C:\\GoMMO\\GoMMO_Client.exe",
"args": ["-game", "rf2232", "-rf", "C:\\RF Online", "-headless", "-mcp"]
}
}
}
What the agent can do
Ask the client for the list: MCP tools/list or GET /tools, both generated from the build you are running. GoMMO_Client.exe -verbs lists the script verbs the single-action tools come from.
| Tool | What it does |
|---|---|
observe | Everything at once: position, health, map, the nearest entities with their distances, bag, equipment, skills, chat channels, the party and any invitations waiting on an answer (0.1.3 and later), buffs and debuffs, the cast in progress, the animus and MAU lists (0.2.1 and later), the guild, the race election and the chip war where the game has them, capabilities, standing orders, and the event cursor. |
events | What happened after a cursor: combat, chat, deaths, respawns, level-ups, players arriving and leaving, map changes and party changes (0.1.3 and later), buffs gained and lost and animus and MAU answers (0.2.1 and later), guild, election, chip war and module notices, standing-order notices. Pass back next as the next since. wait_seconds blocks until something happens. |
walk_to, fight, loot_nearby, equip_best | The common actions, each run to completion. fight walks to the target first. |
attack, use_skill, use_item, pick_up, say, whisper, equip, step_toward, stop, ... | One action each, the same as the script verb of that name. |
run_lua | Runs Lua in the client's script sandbox and returns what it returns. No file or network access. |
start_orders, stop_orders, orders_status | Standing orders, below. |
An action that returns without an error was sent. Whether it worked is in the next observe or in events: the server decides whether a swing hits or an item can be picked up.
say sends a line starting with % as a GM command in RF Online (0.2.0 and later), as the original client does, so say "%lv 20" works where the server allows player commands.
Health, experience and effects
GoMMO Client 0.2.1 and later.
In RF Online, vitals in observe carries:
| Field | |
|---|---|
hp, max_hp | health |
fp, max_fp | force points. mp and max_mp carry the same numbers, for scripts written before 0.2.1. |
sp, max_sp | stamina |
exp_percent | how far through the current level the character is, 0 to 100 |
level | the character's level |
The server sends experience only as a share of the level, so the client cannot give a raw count or the amount still needed.
effects lists the character's buffs and debuffs, each with its name, whether it is harmful, and seconds_left when the server gave a duration. events reports effect_gained and effect_lost.
casting names the skill or force whose result the server has not answered yet, and is absent the rest of the time.
A monster's level comes from your RF install. The server sends a monster's health only as a percentage, so for a monster hp is out of 100 and hp_percent says so.
Skills and forces
GoMMO Client 0.2.1 and later.
skills in observe lists the force window first, then the weapon skills a character trains (Slash, Multi Shot and the rest), then its class skills. use_skill takes the slot shown. Each entry has:
| Field | |
|---|---|
name | from your RF install, in its language |
kind | force, skill or class_skill |
cost | FP per cast |
range | a force's reach, or what a skill adds to the weapon's reach |
cooldown_seconds, ready_in | the delay between casts, and how long until this one can be cast again |
level, mastery | the level it casts at (1 to 7) and the use count that level grows from |
An attack skill needs a target. A support skill, or any skill or force given no target, is cast on the character. The names, costs, ranges and cooldowns are read from your RF install; without one, forces are listed as Force <n> and skills and class skills are not listed.
Class skills are the ones a character's class and the classes it came from grant, so a Berserker keeps the class skills of the classes it was before. They always cast at level 1. An attack class skill given no target goes at the selected one, and with nothing selected it is not sent. A curse goes on its target, and anything else on the character. events reports a refused class skill as refused with the reason, and one that went off without effect as no_effect.
Class changes
GoMMO Client 0.2.1 and later. RF only.
| Tool | What it does |
|---|---|
rf_class | the character's class: its row and code, the classes it came from, and how many class skills it may cast |
rf_class_options | the classes it can promote into, from your RF install, with any reward items and whether one must be chosen |
rf_class_probe | ask the server whether the character may change class now |
rf_class_change | class (a code such as BWF1, or a row) and an optional reward slot: promote |
rf_class_of | another player's class, once the server has reported a change |
A promotion takes effect as soon as the server accepts it, with no relog: observe carries the new class, and the class skills the new class grants join skills behind the ones the character keeps, so no slot it already had moves. events reports offered when the server prompts a promotion, probe with its answer, changed, refused with the reason, no_answer when the server stays silent for 15 seconds, and other_changed when another player promotes. The server asks for level 30 to leave a base class and 40 to leave a first class. A class whose reward must be chosen needs a reward slot from rf_class_options, and -1 sends no choice. On RF 1.5 the reward items are not listed.
A character's forces come from the force items it has learned. To give an existing character every skill and force, type %allskills while player GM commands are on.
Animus
GoMMO Client 0.2.1 and later. Cora only.
| Tool | What it does |
|---|---|
pets | the animus window: each with its slot, name, level, experience, HP and FP, and whether it is out |
pet_refresh | ask the server for the list again |
pet_summon | slot: summon one |
pet_dismiss | send it home |
pet_command | attack (it picks its own fights) or follow (it stays with you) |
pet_attack | set it on a target |
observe carries the list as pets, and events reports each answer: summoned, returned, level_up, died, or refused with the reason. The server refuses an animus under 30% of its health, a summoner under 60 FP, and a second summon within 30 seconds.
There is no feeding. A resting animus heals over time, and pet_refresh shows how far it has recovered.
MAU
GoMMO Client 0.2.1 and later. Bellato only.
| Tool | What it does |
|---|---|
vehicles | the MAU list: each with its slot, frame name, hull, booster, fitted parts, magazines and spare packs (named, with a full magazine's size), how many spare packs its back carries, the fee for calling it out, and where it is parked |
vehicle_frames | the frames you can buy, with each name and its price before tax |
vehicle_buy | frame: buy a new MAU; outside RF 1.5 its key goes into your bag |
vehicle_fill_spares | slot: fill the spare packs (for dalant, except on RF 1.5), topping up each pack that is not full with what it holds, or with the type ids given, one per pack. Without the client's data only empty packs are topped up, since a part-used pack cannot be told from a full one |
vehicle_refresh | ask the server for the list again |
vehicle_call | slot: have one delivered beside you, for its fee |
vehicle_board, vehicle_leave | climb in, or climb out where you stand |
vehicle_return | send the parked MAU back to storage |
vehicle_repair | slot: restore its hull for dalant |
vehicle_reload | slot: refill both magazines for dalant, with the ammunition they hold, or with the type ids given |
observe carries the list as vehicles, and events reports delivered, boarded, left, returned, repaired, reloaded, bought, spares_filled, towed and destroyed, or refused with the reason. Repair, reload and spare filling are refused while riding. A new MAU carries no spare packs, so filling them waits until a back that holds them is fitted. A MAU is the only thing RF Online repairs: weapons and armour do not wear out.
Frame, part and ammunition names are read from your RF install, in its language; without one they show as MAU frame <n>, MAU <part> <n> and MAU ammunition <n>. vehicle_buy takes a frame from vehicle_frames, and the price is the one before tax: bought reports what was charged. On RF 1.5 only the classic frames can be bought.
Parties
GoMMO Client 0.1.3 and later.
In a game with parties (every RF version the client plays), the agent can invite, join and lead:
| Tool | What it does |
|---|---|
party_invite | invite a player in sight; forms a party if you have none |
party_apply | ask a party leader in sight to take you in |
party_accept, party_decline | answer an invitation or application, by player id or the newest |
party_leave, party_kick, party_disband, party_promote | the rest; the last three are the leader's |
rf_party_lock, rf_party_loot | RF only: close the party to newcomers, and pick how drops are shared |
The party section of observe lists the members, leader first, each with health and mana as a fraction of full, level, map, and where they are. Its requests are the invitations and applications waiting on you. events reports invited, applied, joined, left, member_joined, member_left and leader_changed.
A refused invitation looks the same as an ignored one: the server says nothing unless the player accepts. RF has no "no" message either, so party_decline only forgets the request.
Stores and money
GoMMO Client 0.1.3 and later.
observe carries money (dalant and gold on RF) and the ten nearest stores, each with the NPC standing at it.
| Tool | What it does |
|---|---|
buy | store, slot, count: buy from a store. slot is the goods' place in the store's own list, from 0, in the order its window shows them. |
sell | store, inventory_slot, count: sell what is in a bag cell |
goods | store: what it sells, each with the slot that buy takes and its name |
rf_exchange | RF only: change dalant into gold or back |
Both wait for the server and return ok, a reason when refused (the bag is full, not enough dalant, the store does not sell that), what was spent or earned, and how many bag cells filled. A refusal is an answer, not an error. The bag in observe is refreshed after each one.
No server says what a store sells, so goods reads it from your own RF install: point the client at it with -rf, or run it from inside the RF folder. In a script, lib.buy("potion", 5) finds the nearest store that sells something with that name, walks there and buys it. RF 4.15 does not list goods yet, because the 4.15 client in circulation and the 4.15 server disagree about what some stores stock.
Moving between maps
GoMMO Client 0.1.3 and later.
| Tool | What it does |
|---|---|
portals | this map's portals, each with its index, name and distance (needs the RF install, as goods does) |
use_portal | index: step through one |
recall | go home to your town |
observe lists the portals too. When the map changes, map_name changes, events reports map_changed, and everything the character could see is forgotten and sent again for the new map: players, monsters, NPCs and stores.
Trading with a player
GoMMO Client 0.1.3 and later.
| Tool | What it does |
|---|---|
trade_ask, trade_accept | offer a trade to a player standing close by, or accept one |
trade_offer, trade_withdraw | put a bag cell on the table, or take it back |
trade_money | put dalant or gold on the table |
trade_lock, trade_confirm | both sides lock, then both confirm, and the trade completes |
trade_cancel | walk away |
The trade section of observe shows both sides of the table. events reports offered, opened, their_offer_changed, they_locked, they_confirmed and closed; closed says whether it completed. The server closes a trade the moment either side does something it refuses, such as offering an item that cannot be traded, and the bag is refreshed when a trade completes.
What a game offers
capabilities in observe says which of the sections below this game has, once the client has connected: guild, election, chip_war, race_admin and modules. A script asks the same with has_guilds(), has_elections(), has_chip_war(), has_race_admin() and has_modules().
| Section | Games |
|---|---|
| Guilds, election, chip war | RF 4.55, 4.15, 1.5 and 2.2.3.2 |
| Race offices and tax | RF 4.55 and 2.2.3.2 |
| Server module messages | RF 4.55 and 2.2.3.2 |
Guilds
| Tool | What it does |
|---|---|
guild, guild_members | your guild as the client last heard it, and its members |
guild_create | name: found a guild |
guild_apply, guild_cancel_apply | name: apply to a guild, or withdraw the application |
guild_accept, guild_reject | answer an application; give applicant, or leave it out for the newest |
guild_leave | leave your guild |
guild_refresh | ask the server for the roster again |
guild_query, guild_info | guild_id: ask about another guild, and read what the server said |
RF has no guild invitation: a player applies by guild name and the master or a committee member accepts. A guild is founded by a party, through its leader. Guild war events are not offered, because guild battle is not built.
observe carries the section as guild. events reports kind guild: created, joined, applied, application_cancelled, applicant, member_joined, member_left, left, expelled, disbanded, info, or refused with request and reason.
Race election
| Tool | What it does |
|---|---|
election | the election as the client last heard it: leader, council, candidates, ballot, tax_rate, next_tax_rate, tax_shares, and offer, the seat a patriarch offered you |
election_candidates | ask the server for the candidates |
election_register | stand as a candidate |
election_vote | name: vote for a candidate |
election_offices | ask the server who holds the appointed seats |
events reports kind election: candidates, voting_opened, score, result, council, leader_changed, offices, registered, voted, refunded, or refused.
Race offices and tax
| Tool | What it does |
|---|---|
election_appoint | seat, name: the patriarch offers a seat |
election_answer | accept: answer the seat you were offered |
election_discharge | seat, and optionally name: empty a seat |
race_tax_refresh | ask the server for the rate and the distribution |
race_tax_set | percent: propose the next rate |
race_tax_distribute | shares, a list of {guild, rate}: set how the tax is shared among guilds |
The seats are vice_archon_b, attack_officer_b, defence_officer_b and support_officer_b. The rules, and who may do what, are on Race Offices and Tax.
events reports these under kind election too: appoint_ok, appoint_offered, appointed, appointment, tax_rate, tax_change_accepted, tax_income, tax_shares, tax_shares_set.
Chip war
chip_war is the war as the client last heard it: its phase, and seconds_left, stone_hp, the master_race and the last result when the server has given them. observe carries the same section.
events reports kind chipwar: phase, started, stone_hp (on a change), stone_destroyed, master, ended, pvp_points, next.
Server module messages
A server's Lua modules can declare messages of their own. The client asks for the list after it enters the world.
| Tool | What it does |
|---|---|
module_catalog | the declared messages: module, name, direction, fields |
module_last | module, name: the newest values of a message, nil before one arrives |
module_send | module, name, fields: send a message declared in or both; a wrong field is refused before it is sent |
module_refresh | ask for the list again, after an operator reloads a module |
events reports kind module: declared, message (with module, name and fields), and dropped, a message that did not match its declaration.
Standing orders
A model takes seconds to answer and a fight moves faster than that. start_orders hands the fast loop to a script that runs in the client, and the agent is told only when it has something to decide.
The default script, default/standing, hunts an area. Its parameters:
| Parameter | Default | |
|---|---|---|
x, y | where the character stands | centre of the area |
radius | 300 | how far from the centre it hunts |
drink_below | 0.5 | health fraction at which it drinks |
potion | any item named like a potion | part of the name of the item to drink |
rest_below | 0.35 | health fraction at which it stops and rests |
loot | true | pick up drops after each kill |
avoid_players | false | walk back to the centre when a player comes into the area |
minutes | 0 | stop after this long; 0 runs until stopped |
It raises these notices in events:
| Notice | When |
|---|---|
died | the character died; it has respawned |
player_near | a player entered the area (once per player) |
area_empty | no monster in the area for 30 seconds |
out_of_potions | it needed to drink and had nothing |
done | minutes ran out |
reconsult | every reconsult_seconds (default 60), with health and position |
Pass script to run another script as orders, or source for inline Lua. Your script reads its parameters with params() and raises a notice with notify("reason", details). Starting new orders replaces running ones; stop_orders stops them and halts the character.
A hunting buddy
GoMMO Client 0.1.3 and later.
default/buddy waits for a party invitation, accepts it, then follows the leader and fights beside them: it attacks what the leader attacks and whatever attacks the party, drinks, loots and respawns. It raises invited, joined, left, died, out_of_potions and done.
| Parameter | Default | |
|---|---|---|
accept_from | anyone | a name, or a list of names, whose invitations it accepts |
distance | 60 | how close it stays to the leader when nothing needs fighting |
assist | true | fight the leader's target and the party's attackers |
leash | 250 | ignore a fight further than this from the leader |
loot | true | pick up drops after a kill |
drink_below, potion | 0.5, any potion | as for default/standing |
greet | none | a line to say on the party channel on joining |
leave_alone | false | leave once every other member has been out of sight for a minute |
minutes | 0 | stop after this long; 0 runs until stopped |
Many characters at once
-headless -clients N plays N characters from one process, each with its own throwaway account. -script picks what they do. From GoMMO Client 0.1.3, -params passes the script its parameters as JSON and a script tells the copies apart with client_index().
One API for several characters
GoMMO Client 0.2.1 and later.
-headless -clients N with -serve or -mcp puts all N characters behind one API:
| HTTP | GET /clients lists them by index, with name, map and whether each is in the world. /c/<index>/ in front of any request drives that one: /c/2/observe, POST /c/2/act. A request without it drives character 0. |
| MCP | every tool takes an optional client, the index, and the clients tool lists them |
A character still logging in answers 503.
Choosing the character a new account makes
GoMMO Client 0.2.1 and later. RF Online.
When an account has no character yet, the client makes one. -race and -class choose it:
| Flag | Takes |
|---|---|
-race | bellato, bellato_female, cora, cora_female, accretia, or the race/sex code 0 to 4 |
-class | warrior, ranger, spiritualist or specialist (Accretia have no spiritualists), or a class code such as BWB0. Warrior by default. |
They change nothing for an account that already has a character. A population group's class takes the same names.
default/wander (0.1.3 and later) makes passers-by: each character strolls around an area, rests, and strolls again, on its own timing. With accept_party it accepts the first invitation it gets and turns into default/buddy for the rest of the run, so a town full of wanderers is also a pool of hunting partners:
GoMMO_Client.exe -game rf2232 -headless -clients 50 -script default/wander -params "{\"radius\":300,\"accept_party\":true,\"accept_from\":\"YourName\"}"
| Parameter | Default | |
|---|---|---|
x, y | where the character entered | centre of the area |
radius | 250 | how far from the centre it strolls |
pause_min, pause_max | 2, 10 | seconds it stands between walks |
run | false | run rather than walk |
fight | false | swing at a monster that comes within reach |
lines, say_every | none, 120 | things to say now and then, and roughly how often in seconds |
accept_party | false | accept an invitation and become a buddy |
minutes | 0 | stop after this long; 0 runs until stopped |
Keeping a crowd online
GoMMO Client 0.1.3 and later.
-population crowd.json runs a standing crowd until you stop it, restarting any character that drops (sooner after one that ran a while, more slowly after one that keeps failing at once). Each group is one kind of character:
{
"groups": [
{ "name": "townsfolk", "count": 50, "accounts": "town", "script": "default/wander",
"params": { "radius": 300, "accept_party": true } },
{ "name": "field", "count": 20, "accounts": "field", "script": "default/wander",
"route": [3], "params": { "radius": 250, "fight": true } },
{ "name": "cora", "count": 30, "accounts": "cora", "script": "default/wander",
"race_sex": 2, "params": { "radius": 300 } },
{ "name": "guards", "count": 10, "accounts": "guard", "script": "default/standing",
"params": { "radius": 200 }, "class": "Warrior", "race_sex": 0 }
],
"restart_after": "15s",
"stagger": "200ms",
"status_every": "1m"
}
Every group needs its own accounts prefix; its characters are town000, town001 and so on, so the same crowd comes back each time. One process runs at most 2000 characters.
Where a group lives. A character starts in its race's town: race_sex 0 or 1 is Bellato, 2 or 3 Cora, 4 Accretia. route is a list of portal indexes it takes first, in order, so [3] from the Bellato town is the field next to it; portals lists a map's indexes. default/wander and default/standing then settle where they arrive.
Controlling a crowd while it runs
GoMMO Client 0.1.3 and later.
Add -serve and the population is controlled over HTTP on 127.0.0.1:47800, with the token in automation.token beside the client, as for one character:
| Request | |
|---|---|
GET /population | every group: how many are wanted, running and in the world, restarts and the last error, and each character's name, map and state (?bots=0 leaves the characters out) |
POST /population/groups | add a group: the same object as one entry of groups |
POST /population/groups/{name}/scale | {"count": 80}: new characters log in, the highest-numbered leave |
POST /population/groups/{name}/params | replace the group's script settings; each character picks them up when it next starts |
POST /population/groups/{name}/restart | log the whole group out and back in, which applies new settings now |
DELETE /population/groups/{name} | log the group out and forget it |
curl -H "Authorization: Bearer $(cat automation.token)" -d '{"count":80}' http://127.0.0.1:47800/population/groups/townsfolk/scale
Running a large crowd
GoMMO Client 0.1.3 and later.
| Flag | |
|---|---|
-bot-accounts town | every character gets a stable account and name, town000, town001 and so on, so the same crowd comes back each run with its levels and its bags. Without it each run registers throwaway accounts. |
-stagger 100ms | the gap between one login and the next, 300ms by default. A busy server wants more. |
-memprofile crowd.pprof | rewrites a heap profile every 10 seconds, for go tool pprof |
All the characters in one process share a handful of timers rather than running one each, and share one compiled copy of every script. A server running these clients also sends a character that declared itself scripted other players' movement at most once a second each, rather than every step (THIN_SCRIPTED_MOVEMENT in settings.ini, on by default); real players see everything as before. On a desktop, 200 wanderers in one town used about 110 MB and a quarter of one core in the client, and the server's own load fell by over a third.
HTTP
-serve listens on 127.0.0.1:47800; -serve-addr picks another loopback address. It refuses any address that is not on this machine.
Each start writes a new token beside the client. Every request carries it. From GoMMO Client 0.2.1:
- the file is
automation.tokenon port 47800 andautomation-<port>.tokenon any other port, so instances on different ports never overwrite each other's;-token-filenames it yourself; -serve-addr 127.0.0.1:0takes any free port, and the client prints the address it took asAUTOMATION url=... token_file=...;- a port already in use stops the client with an error instead of starting it without an API.
Before 0.2.1 every instance wrote automation.token, so start each from its own folder.
curl -H "Authorization: Bearer $(cat automation.token)" http://127.0.0.1:47800/observe
| Request | |
|---|---|
GET /observe | the observe snapshot |
GET /tools | every tool with its JSON Schema |
GET /events?since=N&wait_seconds=S | the events tool |
GET /stream?since=N | the same events as a Server-Sent Events stream; reconnect with Last-Event-ID |
POST /act | {"tool": "fight", "args": {"seconds": 20}} runs any tool |
POST /lua | {"source": "return vitals()"} |
GET / POST / DELETE /orders | orders status, start, stop |
An action that cannot finish because the game connection has stopped answering gets 504 at its time limit (0.2.1 and later) instead of waiting forever.
A request with a wrong or missing token gets 401. A request whose Host is not localhost or 127.0.0.1 gets 403, so a web page cannot reach the API through a rebound DNS name.
For server operators
A client under -mcp or -serve declares itself as scripted when it logs in, the same as one running a script. ALLOW_SCRIPTED_CLIENTS in settings.ini decides whether your server accepts it; see Refusing scripted clients. A modified client can leave the declaration out, so the setting is a stated policy, not a detection.