Quick navigation

Go to

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.

9 min read1,880 wordsMarkdown
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.

  1. The connect step emits open77:shell:autoconnect <endpoint> <master-id> through the debug bridge.
  2. 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.
  3. The same startConnection the interface runs then executes: character bootstrap reset, Open77.network.connect, signed resource download, character resolution, pristine load, world handoff.
  4. Progress is followed on two channels: net.state on the bridge (network phase, playerId, error) and the [shell-transition] log lines (bootstrap, pristine load, handoff).
  5. 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.state reads alive=no health=0 while the screen holds and alive=yes health=100 once 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 readable position, and char.state alive=yes. An "active" session is not an incarnation, and position already answers in the menu scene.
  • Never send a server action (teleport, spawn, …) to a client whose char.state says alive=no. That is the "press any key to continue" gate: a tpc received there crashed the client outright, with CrashInfo.json pointing 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; esc or c closes 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

// Guides Β· Lua API

Search the docs

1,905 guides and functions indexed

Try