API & developer tools
Autonomous agent testing
The MCP server and Claude Code skill that let an AI agent test OPEN//77 in the running game: the tool list, the human-path connection, repeatable scenarios, installation, and the rules that keep a verdict honest.
On this page11 sections
Use the agent testing toolkit to launch the local stack, connect a game client, run actions and inspect results from an external automation harness.
Two pieces, both versioned in the platform repository:
| Piece | Path | Role |
|---|---|---|
| MCP server | scripts/agent-mcp.ps1 |
exposes the whole loop as typed open77_* tools over MCP (stdio) |
| Claude Code skill | .claude/skills/open77-testing/SKILL.md |
teaches an agent when and how to use them |
The MCP server is a plain PowerShell 7 script with no dependencies and no build step; it starts with the agent session and ends with it. Screenshots come back as real images in the conversation, so a multimodal agent sees what the player sees.
What this means for server owners
Open77 development is validated in the running game, not only in unit tests. The loop has been proven end to end: stack launch, enrolment with the master, connection, pristine character load, crossing the "press any key to continue" screen, a server teleport confirmed by reading the player's position, and screenshots at every step β including two clients connected to the same server under two distinct accounts, each seeing the other's puppet and nameplate on screen.
The same toolkit applies to your own RP server and resources. open77_client_connect takes the
server endpoint, open77_chat speaks through the real chat pipeline β the thing RP servers are
built on β and open77_scenario turns a validation that passed once into a repeatable regression
test with assertions and automatic failure screenshots. An agent asked to implement or validate a
feature is expected to run this loop itself rather than ask a human to launch the game and report
what they see.
The loop
open77_status # always first: what is already up, and how much VRAM is left
open77_build # only after changing C++ β stops clients, builds, verifies deploy (~4 min)
open77_stack_up # master (8090) + dev server (UDP 11778) + one client + debug bridge
open77_client_connect # full human login path; returns only when the player is alive in-world
β¦ act and observe β¦
open77_down # stop_game / stop_server to tear down
Changed only Lua? Skip open77_build: open77_resource_sync copies the resource and reloads it.
Server-only resources are served live from the repository β nothing to copy.
For a complete script-based example, follow Test RTTI in game.
It covers process selection, agent-play.ps1, direct bridge commands, queued
results, multiplayer laboratory access, and hook/reference cleanup checks.
The tools
The authoritative list is the $script:Tools table in scripts/agent-mcp.ps1, which also carries
the full input schema of every tool.
| Tool | Role |
|---|---|
open77_status |
Stack state: master (8090), dev server (UDP 11778), free VRAM, and every game instance with its bridge, session phase, and position. Always start here. |
open77_build |
Rebuilds and redeploys the client through the repository's build-all script (-Target client by default). Stops all games first β a mapped DLL cannot be replaced. Takes ~3β4 minutes; tests are skipped by default for fast iteration. |
open77_stack_up |
Brings up the stack: local master, dev server, one game client with a reachable debug bridge. Idempotent. |
open77_client_launch |
Adds a game instance without touching the others, with a VRAM-margin guard (force to override). identity_profile gives the instance its own account; player_name names a freshly created one. |
open77_client_connect |
Connects an instance through the complete human login path β master enrolment, connection, resource download, pristine load, continue screen β and returns only on proof of life (char.state alive=yes). Takes 1β3 minutes. |
open77_resource_sync |
Copies and reloads a client-side Lua resource without rebuilding the DLL, and returns the resource generation to prove the reload. Explicit reload is refused during an active session β sync before connecting, or disconnect first. |
open77_cmd |
The main verb: a text command to the instance's debug bridge (one-line OK/ERR reply). Unknown bridge commands are forwarded to the server as slash commands (tpc, goto, spawn, car, revive, β¦), subject to the same ACL a player faces. open77_cmd help lists the whole bridge table. |
open77_snapshot |
The data photograph, as structured JSON: network session, character (position, health, weapon, vehicle, locomotion states), camera, vehicles, and optionally nearby entities. The source of truth for what is happening in-game. |
open77_nettrace |
Packet-level telemetry: sent/received counters plus recent packets as direction:kind:sequence:bytes:reliable. The channel for replication desyncs, which a screenshot cannot show. |
open77_chat |
Sends a chat message as this client through the real chat pipeline (a text starting with / is treated by the server as a command). |
open77_screenshot |
Captures the game window and returns it as a PNG image β the eye of the loop, for UI, rendering, and catching a modal panel that has swallowed input. |
open77_input |
Keyboard and mouse injection (SendInput): walk with keys=["forward"] and a long hold_ms, turn the camera with relative mouse_move_x/y, use or enter a vehicle with f, jump with space, fire with click=left, aim with click=right. Movement keys are physical scan codes, valid on any keyboard layout. |
open77_scenario |
Runs a declarative list of steps β each a tool call with an optional regex assert on the result text β stopping at the first failure (unless continue_on_error) and screenshotting failures. Returns a per-step pass/fail report. |
open77_logs |
Tail of the client, dev server, or master log, with an optional regex filter. Useful client filters: shell-transition (connection), error. |
open77_crashinfo |
Crash triage: the most recent CrashInfo.json, the client log tail, and the list of still-alive instances β the moment a client dies. |
open77_down |
Cleanly disconnects an instance; stop_game also stops the process, stop_server stops the dev server and master. |
Prefer commands over input: they are deterministic and do not need window focus.
How the connection works
The path is exactly the one a human clicks; nothing is bypassed.
- The connect step emits
open77:shell:autoconnect <endpoint> <master-id>through the debug bridge. - The shell resource receives the event and refreshes the given master's catalogue, which enrols the identity with that authority β a certificate is issued by a single authority, so a client enrolled elsewhere is rejected by the local server.
- The same
startConnectionthe interface runs then executes: character bootstrap reset,Open77.network.connect, signed resource download, character resolution, pristine load, world handoff. - Progress is followed on two channels:
net.stateon the bridge (network phase, playerId, error) and the[shell-transition]log lines (bootstrap, pristine load, handoff). - The game then shows "press any key to continue" β sometimes tens of seconds after the handoff.
The connect step presses Enter until proof of life:
char.statereadsalive=no health=0while the screen holds andalive=yes health=100once in-game.
Observing β data first, pictures second
Raw state comes before the image. Position, health, network phase, and entities are read through the bridge; the screenshot only verifies what the player sees.
| Question | Source |
|---|---|
| Everything essential, structured | open77_snapshot (JSON: network, character, camera, vehicles) |
| Are packets flowing, in which direction? | open77_nettrace |
| Does chat work end to end? | open77_chat, then a screenshot (the message fades after a few seconds) |
| Why did this client die? | open77_crashinfo |
| Which network phase is the client in? | open77_cmd with net.state β offline/resolving/connecting/handshaking/active/failed/rejected |
| Is the player incarnated, and where? | open77_cmd with position (ERR player_unavailable otherwise) |
| What happened during the connection? | open77_logs with match=shell-transition |
| Are the server resources active? | open77_cmd with resource.distribution |
| What is on screen? | open77_screenshot |
During an active session the bridge is deliberately restricted to read-only diagnostics and safety
hatches. Acting in the world goes through server commands β forwarded automatically by
open77_cmd for any unknown command name β under the server's ACL, the same authority path a
player follows.
Repeatable scenarios
open77_scenario turns a manual validation into a regression test the moment it passes:
{"steps": [
{"name": "session is active", "tool": "open77_cmd",
"args": {"command": "net.state"}, "assert": "phase=active"},
{"name": "player is alive", "tool": "open77_cmd",
"args": {"command": "char.state"}, "assert": "alive=yes"},
{"name": "chat delivered", "tool": "open77_chat",
"args": {"text": "regression check"}, "assert": "chat_sent"}
]}
Each step is an open77_* tool call; a failed assertion is reported with the actual output and a
PNG of the screen at the moment of failure.
Several players at once
open77_client_launch with identity_profile gives an instance its own account
(player_name names a freshly created one); without it, every instance shares the same identity
file, so the same account under the same name. Connect instances one at a time β crossing the
continue screen goes through the keyboard, hence the foreground β and pass pid to every
subsequent call. Two in-world clients is the practical ceiling on a 12 GB graphics card.
Rules paid for by real failures
Run the checks below to distinguish a completed operation from an accepted command or a partially loaded world.
- World entry is proven by three things together: the log line
worldReady matched pristine transition, a readableposition, andchar.state alive=yes. An "active" session is not an incarnation, andpositionalready answers in the menu scene. - Never send a server action (teleport, spawn, β¦) to a client whose
char.statesaysalive=no. That is the "press any key to continue" gate: atpcreceived there crashed the client outright, withCrashInfo.jsonpointing at the teleport destination. This is why the connect tool refuses to return before proof of life. - A launched instance takes 60β150 seconds to appear. A process shows up after roughly a minute and its window some ten seconds later. Do not conclude failure early.
- VRAM is the real limit on concurrent clients β roughly 2.7 GiB at the menu and up to 4.2 GiB in-world per instance. Lowering graphics settings barely helps, because the streamer budgets against the card, not the settings. An instance without VRAM dies silently.
- A stray keypress can open a modal panel (the radio, for instance) that then eats all
gameplay input. Screenshot to see it;
escorccloses it. - Deploying a DLL requires every game stopped; Lua-only changes hot-reload.
- Explicit resource reload is refused during an active session β sync before connecting, or disconnect first.
- All instances share one save folder, and autosave starts on world entry. Keep a single client in-world when it matters, and back the folder up before a multi-client session.
Installation
1. Declare the MCP server
Declare the server in the .mcp.json your MCP client reads (a complete template ships as
.mcp.json.example at the repository root):
{
"mcpServers": {
"open77": {
"type": "stdio",
"command": "pwsh",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass",
"-File", "<path-to-repo>/scripts/agent-mcp.ps1"]
}
}
}
Under Claude Code, a project .mcp.json must be approved: add "open77" to
enabledMcpjsonServers in .claude/settings.local.json.
Prerequisites: PowerShell 7 (pwsh), the OP77_GAME_DIR environment variable pointing at the
game installation, and a deployed Open77 client (./scripts/build-all.sh).
2. Install the skill
.claude/skills/open77-testing/SKILL.md is versioned: a clone of the repository has it already.
Claude Code discovers project skills in .claude/skills/ at the session root. If the session
is opened on the repository itself, nothing to do.
If the session is opened on a parent folder, make the folder visible from that root β a junction avoids maintaining two copies:
New-Item -ItemType Junction -Path '<session-root>\.claude\skills' `
-Target '<path-to-repo>\.claude\skills'
To install machine-wide rather than per project, copy the open77-testing folder into
~/.claude/skills/.
Skills are loaded at session start: after installing, open a new session. Verify with
/skills, or simply ask for a testing task β the agent should load it on its own.
3. Verify
In a new session, on the repository:
open77_status
The reply reports the master (8090), the dev server (11778), free VRAM, and any running game
instances. From there the whole loop is two calls: open77_stack_up, then
open77_client_connect.
Extending the toolkit
The MCP server is a versioned script: adding a tool means adding an entry to the $script:Tools
table in scripts/agent-mcp.ps1 β a description, an inputSchema, a handler β and restarting
the session. An agent blocked by a missing tool can therefore create it itself; that is
deliberate. Any durable addition deserves a line in the skill and in the reference guide.
Where the details live
See docs/agent-autonomous-testing.md for the automation reference and docs/agent-setup.md for installation. The MCP server wraps the standalone agent-play.ps1, capture-game.ps1, game-input.ps1 and launch-extra-client.ps1 scripts.
// For agents & LLMsthis page as Markdownllms.txtllms-full.txt
