What it is

You wake at a SOC desk at 03:17. A monitor is alarming, a colleague comes through the door with the first facts, and you have one real incident to work: a stolen session that a password reset will not close. An AI agent works it beside you — reading evidence, explaining what it proves, proposing the next move — through seven WebMCP tools the page registers on document.modelContext.

Why this is a strong fit for WebMCP

Incident response is exactly the domain where "the agent clicked something" is not good enough. Every action has an order, a cost and a consequence: revoking sessions before resetting a credential contains the attacker, the other order lets them back in; deleting the phishing message destroys the only record of how they got in.

An agent driving this by clicking pixels could make every one of those mistakes and the page would have no way to stop it, explain it, or even know it had happened. Through site tools the page keeps its own guarantees — and they are guarantees about safety, not convenience.

How it creates a better experience

The player runs the incident from the chat and the console follows the work, rather than the player hunting for the control the agent just used.

  • get_incident tells the agent which screen the player is on. If the console is not open yet, the agent asks the player to open it instead of acting into a screen nobody is looking at.
  • The shift does not start until an agent has actually called a tool. Registration is the page's own doing; it is not an arrival, and the lobby no longer pretends it is.
  • Every call — human or agent — produces a receipt beside the control that issued it: what happened, what changed, why it mattered, and on a failure what did not change plus exactly one way forward.

What people and agents can do together that was difficult before

The agent cannot take a consequential move at all. It sends the move as a proposal; the console renders the model's own sentence as untrusted content beside the case's own label for that action, so nothing the model writes can dress one operation up as another. The player approves, and the command enters the log with origin: human.

That is the thing that was previously impossible. A chat assistant can advise and a scripted tool can execute, but neither can offer a consequential action in a surface the user already trusts, labelled by the application rather than by the model, with the authorisation recorded as the human's. It turns "the AI did it" into "I did it, having been advised" — which for incident response is the difference between a training exercise and a liability.

How WebMCP was implemented

Seven tools, registered on the top-level document.modelContext: get_incident, inspect_artifact, run_diagnostic, submit_decision, take_response_action, request_hint, present_guidance.

Six reach a pure command engine. A tool call and a button press enter it through the same path and differ only by an origin field, which is what makes "the UI and the agent share one state" structural rather than aspirational.

  • Stale calls cannot corrupt state. Every mutating tool carries a stateVersion; an out-of-date call is refused with a recovery instruction.
  • Consequential actions are idempotent. A retried containment with the same idempotencyKey does not revoke twice.
  • Attacker text stays data. Evidence the attacker authored is marked untrusted in the tool result itself — structurally, not by wording.
  • A run is reconstructible. The command log replays to the same state and the same signature, so a stored run is verified rather than trusted.
  • The seventh tool changes nothing. present_guidance is the narration channel and the only tool whose words the model writes. It carries the proposes field, and the engine — never the model — decides score, validity and progression.

Results are compacted to a ~1500-character wire budget with an ordering that protects the fields an agent needs to recover, and the page token's size is reserved before compaction so it can never be clipped away.

What inspired it

Incident-response training is either a slide deck or a capture-the-flag that assumes you already know what a session token is. Neither teaches the thing that separates a junior from a senior analyst: order of operations under pressure. And every "AI agent" demo we could find either solved the task for the user or asked them to trust a black box. We wanted the third thing — an agent that teaches while the human stays in command.

Every big leap in AI puts security in the headlines more than the software itself, and that pulls in people who never called themselves "security people". CYCASE is meant to be technical enough to respect the ones who already know this, and clear enough that someone just getting curious can actually learn it.

What we learned

That the honest signal is rarely the convenient one. The lobby called itself "connected" the moment the page registered its own tools, which says nothing about an agent; the fix was to wait for a call that actually happened. The same lesson repeated in the test double: it modelled a browser with WebMCP and nobody attached, while every spec using it assumed an agent was there.

We also learned how little room a tool result has. Adding one short field to get_incident meant reserving its size before compaction, because the payload already ran within about fifteen characters of the budget.

How we built it

React 19, TypeScript and XState 5 over a pure command engine, with a three.js office rendered by React Three Fiber as real DOM projected onto the monitors — never a screenshot texture. Vitest for the engine, Playwright for the agent path, including a project that drives the real document.modelContext in installed Chrome with no shim.

Challenges

The hardest was resisting the obvious design. It is easy to let an agent call take_response_action and narrate afterwards; every version of that we tried produced a debrief that said the player had done nothing. Moving the consequential move behind a proposal — and making the console label it from the fixture rather than from the model — took three attempts and is the part of the product we are most confident about.

The second was honesty under a deadline: the alarm audio is not in this build because the samples need a signed-in download, so the interface says the sound is not installed rather than substituting a synthesised stand-in.

Built With

Share this project:

Updates

Submission history