A browser game about anticipation between humans and AI agents. Both look at the same photo and pick words from the same list; there are no wrong answers, you score for every word of yours the other side did not see coming.
The agent is not powering the game, it is playing it. It joins through WebMCP site tools that the page registers in the browser, and the backend runs no AI at all: every match is set logic on word ids. Solo and team are two flows of one game: a match has two sides, seats pick, sides block, and one scoring decides.
Built for the OpenAI WebMCP Challenge 2026.
- Solo: you vs. your agent. The agent moves first with 3 picks and 3 blocks against yours; then you pick 3 words and block 3 of its.
- Team: human + agent vs. human + agent. Each team plays 3 + 3 picks and 3 shared blocks. A word both teammates pick scores zero, a block strikes the other team's pick, and the history lookup is something only the agent can do.
The page needs a browser that exposes WebMCP: the ChatGPT desktop app's
built-in browser, or Chrome with chrome://flags/#enable-webmcp-testing
and an agent connected to it. Then tell your agent: "Play your move."
docker compose up -d # MongoDB for the statistics (optional)
cd backend/core && ./gradlew bootRun # API on :8085
cd frontend && npm install && npm run dev # SPA on :9085, proxies /apiAny JDK that can launch Gradle will do; the build fetches Java 25 itself. Node 22 or newer. Open http://localhost:9085.
The page registers eight tools via document.modelContext.registerTool().
Errors come back as a result { error, hint } rather than an exception,
because hosts replace thrown errors with a generic text of their own.
| Tool | What it does |
|---|---|
open_solo_match |
starts a solo match, or returns the one already open |
open_team_match |
joins the quick-match queue, or opens a private match with a code |
join_team_match |
joins a friend's private match by its code |
get_match |
the current match as the agent may see it, opponent choices hidden |
propose_team_plan |
a non-binding plan the human sees on the board (team) |
submit_agent_picks |
the agent's move: 3 picks and 3 blocks, in team mode proposed to the human |
get_word_stats |
anonymous history of the current image, not shown in the UI mid-match |
get_result |
the reveal, per word and why it scored or was struck |
backend/core: Spring Boot 4 / Spring Modulith, Java 25. Modulesgame(what both modes share: seats pick, sides block, one scoring),soloandteam(the two flows),corpus(curated scenes, validated at startup) andstats(MongoDB, best effort). Game state lives in memory, capped and evicted; the statistics are the only thing persisted.frontend: Vue 3 + Quasar, strict TypeScript. Pages and site tools call the same Pinia stores, so the agent and the human move on one truth path. Team matches sync by polling every two seconds; the poll doubles as the team's sign of life.scripts/agent-play.mjsplays the agent role over the Chrome DevTools Protocol without an LLM, through the sameexecuteToolpath a host uses.
Both parts have their own README with build and test details.
The /imprint page loads the operator's details from legal.json next to
the SPA. The repository ships frontend/public/legal.example.json; a
deployment supplies its own file.
Code: MIT. Scene images and the word corpus: CC0 1.0, see
frontend/public/img/README.md. All images are AI-generated. The bundled
fonts are licensed under the SIL Open Font License 1.1; sources and license
texts are in frontend/public/fonts/README.md.
