Drive a session from Claude Code
This tutorial connects Claude Code to a Chrome session in Kasm and has it complete a task on a real web page. Kasm Session Runtime runs inside the session as an MCP server. Claude Code does the reasoning and calls the runtime’s tools: it lists the page’s interactive elements, clicks one by the handle the listing gave it, and checks where it ended up.
The runtime needs no model for this, since Claude Code does the reasoning. The dom.* tools the task uses work without one; only the screen.ask, screen.ground and screen.judge tools need a vision endpoint configured in the session.
The run uses the 05-mcp-drive example in the SDK, and its INSTRUCTIONS.md, which is written for an agent to follow.
Before you start
Section titled “Before you start”You need:
- A Kasm Workspaces deployment you can reach.
- A Kasm API key and secret that can set environment variables when it requests a session (
request_kasm), plus the username of the user it acts as. The runtime switches on only when the session’s environment carriesKASM_RUNTIME_SESSION_TOKEN, and Kasm drops that environment for a caller without an API key. The key also registers a port map on the image the first time, which needs theIMAGES_VIEW,IMAGES_MODIFYandIMAGES_MODIFY_RESOURCESpermissions. - The username and password of a real Kasm user. Kasm’s reverse proxy lets a request through to the session’s MCP endpoint only with that user’s login cookie.
- A kasm-nix Chrome image with the runtime built in. The example defaults to
chrome:nix. The walkthrough was verified on the image published 2026-09-04. - Claude Code, uv and
python3on your machine. - Outbound internet access from the session: the task uses
pcpartpicker.com.
1. Fill in the settings file
Section titled “1. Fill in the settings file”In the SDK’s examples directory, copy settings.env.example to settings.env and fill in the Kasm block. Keep every line an export.
export KASM_URL=https://kasm.example.comexport KASM_API_KEY=...export KASM_API_KEY_SECRET=...export KASM_SESSION_USER=...export KASM_USERNAME=...export KASM_PASSWORD=...export KASM_VERIFY_TLS=trueIf you have only a username and password, the helper can mint a scoped API key from an account that holds global admin rights. Optional:
uv run lib/kasm_provision.py bootstrap-api-keyLeave the vision endpoint lines commented out. Without them, the screen.* tools answer with an error and the dom.* tools work as normal.
2. Start the session
Section titled “2. Start the session”cd 05-mcp-drive./session.sh upThe script:
- Registers the
mcpport map (port 9434) on the image, once. Running it again changes nothing. - Generates a random session token and requests a session with
KASM_RUNTIME_SESSION_TOKENset to it. The runtime in the image starts only when that variable is present. - Opens the session’s viewer in your browser. Set
NO_OPEN=1to skip that on a machine with no browser; the link is printed either way. - Prints the MCP connection details.
The output ends with a line you can paste:
claude mcp add --transport http kasm-session "https://<host>/desktop/<kasm_id>/mcp/mcp" \ --header "X-Kasm-Runtime-Session-Token: <token>" --header "Cookie: session_token=<...>; username=<...>"Both headers are required. The token goes in X-Kasm-Runtime-Session-Token because Kasm’s proxy overwrites the Authorization header.
The script keeps the session id and token in .session in the same folder, so ./session.sh connect can print the details again and ./session.sh down can destroy the session from a fresh shell.
3. Register the server with Claude Code
Section titled “3. Register the server with Claude Code”Run the claude mcp add line the script printed. Then start Claude Code in the same folder. If it needs to reconnect later, use /mcp inside Claude Code, or run ./session.sh connect for the details again.
4. Confirm the connection
Section titled “4. Confirm the connection”Ask Claude Code to call dom.url with no arguments. On a fresh session it names Chrome’s new-tab page, chrome://new-tab-page/ or chrome://newtab/.
If it fails:
| Error | Meaning |
|---|---|
| 401 | The session token header is wrong |
| 502 | The runtime is not up yet (wait a few seconds), or the port map is missing |
5. Give it the task
Section titled “5. Give it the task”Point Claude Code at INSTRUCTIONS.md in the same folder, or give it the goal directly. The goal, from that file:
This page lists products in one category (e.g. CPUs). Calldom.list_interactive to see the products, dismiss any cookie banner ifpresent, then click 'Add' on any one suitable product usingdom.click_described. Do not check whether other parts are alreadyselected -- that is not needed. Don't ask follow-up questions; use your bestjudgement.The page is https://pcpartpicker.com/products/cpu/. The session starts on a blank tab, so opening it with dom.navigate is part of the task and gets recorded like every other step.
The tools it will use:
| Tool | What it does |
|---|---|
dom.navigate {url} |
Opens the page and returns once it has loaded |
dom.list_interactive {filter?} |
Lists what is on the page as {handle, role, name, disabled}. On a busy page, pass a filter |
dom.click_described {description} |
Clicks one element, by a handle from the listing it just got, or its exact name |
dom.wait {predicate, timeout_s} |
Waits until a JavaScript predicate on the page is true |
dom.url |
Returns where the tab is now |
screen.judge {expectation} |
Asks the vision model whether something is visibly true. Needs a vision endpoint |
screen.capture |
Takes a screenshot |
A verified run took eight calls:
dom.navigateto the category page.dom.list_interactivewith filter “Allow”, thendom.click_describedon the cookie button’s handle.dom.waituntil the product list is back, because the consent click re-renders the page.dom.list_interactivewith filter “Add”, thendom.click_describedon one product’s Add button.dom.waitfor/list/in the URL, thendom.url, which readshttps://pcpartpicker.com/list/.
That run ended with a screen.judge call. Without a vision endpoint that call returns an error, and the dom.url result is the confirmation.
Handles are five characters and come from the latest listing. If the agent guesses or reuses one, the runtime refuses the click with “not a handle on this page”.
The unfiltered listing on this page has more than 130 entries. A filter keeps the agent’s context small.
A click returns as soon as it is delivered, before the next page has loaded, so the sequence waits with dom.wait before reading dom.url.
6. Watch the sidebar
Section titled “6. Watch the sidebar”Switch to the session’s viewer. The sidebar on the right lists every tool call Claude Code makes, with its result, as it happens. Anyone who opens the viewer later, while the session is still up, sees the run replayed.
If the sidebar says “Waiting for the runtime”, the runtime did not start: check that the API key is in settings.env, because without it the session token never reaches the session.
7. Destroy the session
Section titled “7. Destroy the session”Do this whether the task succeeded or not:
./session.sh downclaude mcp remove kasm-sessionA session left running holds a container and a seat. The MCP registration holds a Kasm login cookie, so remove it too.
Limits of this run
Section titled “Limits of this run”- The example does not collect a report file from the session. The sidebar is the only record.
- It depends on a live public site. If
pcpartpicker.comchanges its layout, the goal may need rewording. - Any MCP client can connect to the same endpoint with the same two headers.
session.sh upalso prints a config block for another client alongside the Claude Code line.
- MCP methods: every tool, its arguments and its errors.
- Enable the runtime on a workspace: set the token on a workspace so every session has the runtime switched on.
- Run it with a local model: the same task with the runtime’s own loop doing the reasoning.