Skip to content

Add MCP server so agent harnesses can play browser games - #30

Open
Martian-dev wants to merge 1 commit into
parsewave:mainfrom
Martian-dev:mcp-agent-player
Open

Add MCP server so agent harnesses can play browser games#30
Martian-dev wants to merge 1 commit into
parsewave:mainfrom
Martian-dev:mcp-agent-player

Conversation

@Martian-dev

Copy link
Copy Markdown

Adds an MCP surface where the connected agent is the player. The existing CLI runs its own VLM loop and produces a playtest video; here Claude Code (or any MCP harness) observes a frame, sends a timed input sequence, and observes the next one. Aimed at goal-directed play — reach a location, capture it — rather than footage generation.

Approach

The server embeds the controller instead of driving the daemon over HTTP. That works because BrowserSession takes (config, paths, profiler) as constructor args and only reads this.paths.runDir, and runStep takes everything by parameter. Bypassing daemon.js/api.js drops the session files, the unauthenticated loopback port, the process.exit in stop, and the cwd-captured-at-require-time in paths.js in one move.

Reused unchanged: session-factory, browser-session, the normalize → timeline → execute chain, state-reader, grid-overlay, output-writer, protocol/.

Recording is out of scope for interactive play, so Chromium runs headless and the gstreamer, PulseAudio, and Xvfb requirements are gone. Requirements are now Node 20+ and Chromium's system libs. mcp/Dockerfile starts from the Playwright base image, which already has them.

Tools

launch_game, observe, act, zoom, capture, reset_game, journal, list_sessions, end_game.

act carries the action schema as real JSON Schema derived from protocol/src/action.js, so MAX_ACTION_SPAN_MS and the field allowlists can't drift from what the executor enforces, and malformed sequences are rejected before any input is sent.

Adaptations for an agent player

  • Frames return as MCP image blocks, downscaled by default. A 1280x720 PNG is ~1200 tokens against ~300 at half scale. Over a long navigation that dominates every other cost. zoom gives full-resolution detail on a region without paying for a full frame.
  • act reports whether the frame changed, from a file hash. A byte-identical frame almost always means the input never reached the game rather than the game ignoring it, which is otherwise easy to misdiagnose.
  • Grid overlay off by default. It enlarges the PNG with a label margin per side, so pixels read off the image don't match pixels sent back as x/y. When enabled, the offset is stated in the response.
  • Grid cells resolve to the cell centre, via a new opt-in markGridSampleMode. Playtest scatter varies footage but misses precise targets and makes runs unreproducible. Verified: 1 distinct point across 300 runs with the flag, 287 without. The playtest default is unchangedagent/'s separate normalizer copy also still scatters.
  • Per-turn state drops the WebGL probe and keeps the largest canvas as game_area. The probe is useful in a playtest report and pure overhead on every turn of a navigation.
  • Calls serialize per session. One Playwright page and one shared step counter mean concurrent calls would interleave keypresses and collide on output filenames. Subagents can share a session_id safely.
  • Lifecycle handling. SIGINT/SIGTERM/SIGHUP/uncaughtException close every browser and game process group; a 30-minute idle timeout covers a forgotten end_game. Chromium and game processes are detached children, so without this a host shutdown orphans the tree.

Changes to existing code

Deliberately minimal and additive: 4 lines plus one new gridSampleMode function in protocol/src/mark-grid.js and its call site in controller/src/action-normalizer.js. Both default to current behaviour.

Testing

121/123 pass, 0 fail (npm test). 12 new unit tests cover config defaults, cell determinism, scatter preservation, image resize/crop, state compaction, and the action schema.

The integration test has not run. It drives real headless Chromium against a fixture game — held-key movement, a grid-cell click onto an 80x80 target, frame-change detection — but the machine I built this on is missing libnss3/libnspr4, so it skips itself with a message rather than passing vacuously. Everything above the browser boundary is unit-tested; the play loop end to end is not verified yet. npm run test:mcp on a host with the libs (or in the provided Dockerfile) is the real go/no-go, and worth doing before merge.

🤖 Generated with Claude Code

Runwave's CLI runs its own VLM loop and produces a playtest video. This
adds an MCP surface where the connected agent is the player instead:
observe a frame, send a timed input sequence, observe the next frame.

The server embeds the controller rather than driving the daemon. That is
possible because BrowserSession and runStep already take their
dependencies as arguments, and it avoids the daemon's session files,
unauthenticated loopback port, process.exit on stop, and cwd captured at
module load. The normalize/timeline/execute chain, session factory,
state reader, and grid overlay are reused unchanged.

Recording is out of scope for interactive play, so Chromium runs
headless and the gstreamer, PulseAudio, and Xvfb requirements are gone.

Adaptations for an agent player rather than a playtest bot:

- Frames return as MCP image blocks, downscaled by default. A 1280x720
  PNG is ~1200 tokens against ~300 at half scale, which dominates cost
  over a long navigation. zoom covers detail without a full frame.
- act reports whether the frame changed. A byte-identical frame means
  the input did not reach the game, not that the game ignored it.
- Grid overlay off by default: it enlarges the PNG with a label margin,
  desyncing image coordinates from input coordinates. The offset is
  reported when it is on.
- Grid cells resolve to the cell centre via a new opt-in
  markGridSampleMode. Playtest scatter varies footage but misses precise
  targets and makes runs unreproducible; the default is unchanged.
- Per-turn state drops the WebGL probe and keeps the largest canvas as
  the game area.
- Calls serialize per session, since one page and one step counter mean
  concurrent calls would interleave keypresses.
- Signal handlers and a 30 minute idle timeout close detached Chromium
  and game processes.

Tests: 12 unit tests, plus an integration test that drives real headless
Chromium against a fixture game and skips when Chromium cannot launch.
The integration path is unverified here: this machine is missing
libnss3/libnspr4, so it has only been exercised via unit tests so far.

Co-Authored-By: Claude <noreply@anthropic.com>
@parsewave-bot

parsewave-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

TerminalBench Bot Commands

Run tasks:
/bot tb run [--dataset, --dataset-path, --dataset-config, --registry-url, --local-registry-path, --output-path, --run-id, --upload-results, --task-id, --n-tasks, --exclude-task-id, --no-rebuild, --cleanup, --use-subscription, --model, --agent, --agent-import-path, --agent-kwarg, --log-level, --livestream, --n-concurrent, --n-attempts, --global-timeout-multiplier, --global-agent-timeout-sec, --global-test-timeout-sec, --contributionsCommit]

Check:
/bot tb tasks check [--task-id, --tasks-dir, --unit-test-relative-path, --dockerfile-relative-path, --model, --agent, --fix, --output-path, --contributionsCommit]

Debug:
/bot tb tasks debug [--task-id, --run-id, --runs-dir, --tb-run-job-id, --tasks-dir, --agent, --model, --n-trials, --output-path, --contributionsCommit]

Full Check:
/bot full-check-v2 [--task-id <id>] [--analyze-failure] [...]
/bot full-check-v2 --opus-only - Run only tb_run_large with Claude Opus 4.7
/bot full-check-v2 --sonnet-only - Run only tb_run_large with Claude Sonnet 4.5
/bot full-check-v2 --tbench - Oracle + NOP + tb_run_large (Codex + openai/gpt-5.5, 5 parallel attempts, subscription; requires 0-3/5 resolved) + Codex harbor debug/analyze when ≤3/5 resolved
/bot full-check-v2 --tbench --opus - Same as --tbench, but tb_run_large uses Claude Opus 4.8 via claude-code
/bot full-check-v2 --fusion-reports - Oracle + NOP + harbor_run_large (Codex + openai/gpt-5.5, 1 attempt, subscription). No fallback stage. Harbor saves /output, /app/output, and traces per trial automatically (implicit --artifacts — needed for downstream re-verify).
/bot full-check --openclaw - Run Oracle + NOP + exactly 5 SecureHermes trials with GPT-5.6, judge with GPT-5.6-sol, then replace the PR's committed OpenClaw trace folder at the unchanged PR head.
/bot full-check-v2 --oracle-nop-only - Lightweight gate: only Oracle (5 attempts, 5 retries) + NOP, skip every model run / similarity / debug / quality check (~30-60s per task)
/bot full-check --tb-run-large-agent claude-code --tb-run-large-model claude-opus-4-7

Grok Trace Run:
/bot run-grok --5 - Shortcut for Oracle + NOP + 5 Grok Build trials with artifacts/traces.
/bot run-grok --5 --appends - Add 5 Grok traces after the existing S3 traces, then auto-rescore the full S3 trace set.
/bot run-grok --8 - Shortcut for Oracle + NOP + 8 Grok Build trials with artifacts/traces.

Trace Run:
/bot mm-trace-run - Oracle + NOP + 5 Codex GPT-5.5 xhigh trials (subscription) with artifacts/traces. Posts per-trial rewards and step counts, avg reward, avg steps, max/avg reward ratio, and artifact + trace viewer links.

Re-verify (re-score existing agent attempts against updated tests):
/bot re-verify [--task-id <id>] [--skip-oracle] [--skip-nop] - Re-run tasks/<task-id>/tests/test.sh against every trajectories*/<task-id>/<agent>/<N>/artifacts/output/ directory in the PR head ref (1 claude + 4 grok by convention). Also runs oracle (canonical solution/solve.sh, expected reward 1.0) and nop (empty /output, expected reward 0.0) sanity rows by default — pass --skip-oracle or --skip-nop to opt out. Produces fresh per-attempt rewards without re-running the agents — useful after editing tests/ during review.
/bot rejudge --openclaw - Rejudge the 5 OpenClaw Hermes traces already committed to the PR with GPT-5.6-sol. Solver trials are not rerun; refreshed judge evidence is committed only if the PR head is unchanged.
/bot rescore - Re-score the current PR's existing <tasks-dir>/<task-id>/traces/ outputs against current tests after rubric/test-only changes; runs oracle + nop sanity rows by default.
/bot fairness-review - Run the structured task fairness review and render a standardized PASS/WARN/FAIL comment.

Harbor format checker:
/bot harbor-format-check [--trace-s3-url s3://bucket/prefix[,s3://bucket/other-prefix]] [--policy mm-abc|compat] - Run the standardized pre-acceptance format checker on this PR, including LLM fuzzy checks and optional S3 trace checks.

Offline-search reviewer:
/bot offline-search-review [--agents 1-5] - Run the offline-search audit reviewer on this PR and post the auditrobot summary back here.
/bot offline-review [--agents 1-5] - Short alias for /bot offline-search-review.

Online-search reviewer:
/bot online-search-review - Queue the online-search rubric/fairness reviewer on this PR and post PASS/WARN/FAIL here.

Sapphire format checker:
/bot sapphire-format-check [--task-dir tasks/<id>] [--no-llm] [--check-traces] - Run the mm-sapphire-pipelines format checker on this PR without touching full-check or mm-trace-run. Trace checks are opt-in.

Multiturn format checker:
/bot multiturn-format-check [--task-dir tasks/<id>] (alias: /bot mt-fc) - Run the deterministic native-resume package, S3 manifest, and score-parity checker. No model, Harbor, Docker, or trace payload download.

For detailed parameter descriptions, run tb --help or tb <command> --help locally.

Job Management:
/bot job list - List all running jobs
/bot job status <job_id> - Get status of a specific job
/bot job kill <job_id> - Kill a running job
/bot job restart <job_id> - Restart a failed job
/bot job info <job_id> - Show detailed information about a job
/bot job cleanup - Remove all failed-to-report jobs

Review:
/bot code-review - Trigger the generic AI code review service on this PR

Remove default flags: Use --no-{flag} to disable default flags (e.g., --no-use-subscription)

Aliases:
/bot /codex-attempts [--n, --task-id, --tasks-dir]/bot /tb run --agent codex
/bot /claude-attempts [--n, --task-id, --tasks-dir]/bot /tb run --agent claude-code
/bot /grok-attempts [--n, --task-id, --tasks-dir]/bot /tb run --agent terminus-2 --model xai/grok-4.3-internal --agent-kwarg reasoning_effort=high
/bot /tb-check [--task-id, --tasks-dir]/bot /tb tasks check
/bot /oracle [--task-id, --tasks-dir]/bot /tb run --agent oracle
/bot /nop [--task-id, --tasks-dir]/bot /tb run --agent nop
/bot /tb-debug [--task-id, --tasks-dir]/bot /tb tasks debug

Get help: /help or /bot help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant