Enable the runtime on a workspace
This guide switches Kasm Session Runtime on for a Kasm workspace, so an outside agent can drive its sessions over MCP and a person can watch in the sidebar.
The runtime does nothing until a session has KASM_RUNTIME_SESSION_TOKEN set. The variable is both the on switch and the credential. When it is set, the runtime starts its MCP endpoint on port 9434 and every caller must send the same value in the X-Kasm-Runtime-Session-Token header. When it is unset the session behaves like any other.
None of this needs a vision model. An outside agent brings its own. See When you need a model for what a model adds inside the runtime.
Two kinds of image
Section titled “Two kinds of image”| Image | What you do |
|---|---|
Kasm nix images, for example chrome:nix |
The runtime is already in the image. Register the port map and set the environment (part A) |
Stock kasmweb/* images |
Build a thin layer on top of the published image, then do the same as for nix (part B) |
Before you start
Section titled “Before you start”-
Kasm administrator access, to edit a workspace’s Docker run configuration.
-
A token for the session: a long random value. For example:
Terminal window python3 -c 'import secrets; print(secrets.token_hex(16))' -
For stock images only: Docker, and a registry your Kasm deployment can pull from.
Part A: nix images
Section titled “Part A: nix images”1. Register the MCP port map
Section titled “1. Register the MCP port map”Kasm reaches a service inside a session through a custom_port_map entry on the workspace. The runtime needs one named mcp that points at container port 9434. The sidebar uses the same entry, so register it even if only people will look at the session.
Open the workspace in the Kasm admin UI and add this to Docker Run Config Override (JSON) :
{ "custom_port_map": { "mcp": { "container_port": "9434/tcp/http/none" } }}The value is one string, <port>/tcp/<scheme>/<auth>. none means Kasm’s proxy does not add Basic authentication of its own. Kasm’s session cookie still gates the path in every mode.
Kasm applies the change only to sessions requested after it. Sessions already running do not get the port map.
Optional: the SDK does the same from the command line with an API key that has IMAGES_VIEW, IMAGES_MODIFY and IMAGES_MODIFY_RESOURCES. Run it from the SDK’s examples directory with settings.env filled in:
uv run lib/kasm_provision.py ensure-port-map --ref <image> --port 9434 --service mcp2. Set the environment
Section titled “2. Set the environment”Add an environment map to the same Docker Run Config Override (JSON) :
{ "custom_port_map": { "mcp": { "container_port": "9434/tcp/http/none" } }, "environment": { "KASM_RUNTIME_SESSION_TOKEN": "<your token>", "KASM_RUNTIME_SIDEBAR": "default" }}KASM_RUNTIME_SIDEBAR=default shows the runtime’s own sidebar in the viewer. Leave it out for the stock viewer.
A value set on the workspace is the same for every session of that workspace. To give each session its own token, leave it off the workspace and set it when the session is requested (see One token per session).
3. Optional settings
Section titled “3. Optional settings”| Variable | Effect |
|---|---|
KASM_ENABLE_CDP=true |
Also exposes a raw Chrome DevTools Protocol proxy at /cdp/ on the same port, gated by the same token. Ignored, with a log line, when no session token is set |
ACCESSIBILITY_ENABLE=1 |
Turns on the desktop’s accessibility switch before the app starts, for toolkits that publish nothing over AT-SPI otherwise |
VNC_FIXED_RESOLUTION=1 |
Refuses client-driven resizes, so an attached viewer cannot change the screen size during a run. Needs a recent core image; older images ignore it |
KASM_RUNTIME_VISION_ENDPOINT and the other vision settings |
Gives the runtime a model for the screen.* tools. See Serve a vision model |
For the dom.* tools, Chrome has to start with a debugging port. Set APP_ARGS, which replaces the image’s default arguments rather than adding to them. Optional, in the same environment map:
{ "environment": { "APP_ARGS": "--start-maximized --test-type --ignore-certificate-errors --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --remote-allow-origins=* --user-data-dir=/home/kasm-user/.config/chrome-cdp-profile" }}Keep --user-data-dir. Chrome 136 and later ignores --remote-debugging-port without it and gives no error.
A vision-grounded click and a baseline screenshot are only valid at the screen size they were produced at, so a model-driven run needs a fixed size. On nix images Kasm sets VNC_RESOLUTION from the session request’s x_res and y_res, overwriting any value in the environment map, so fix the size in the request. The SDK’s kasm_provision.py up sends x_res and y_res when you pass --env VNC_RESOLUTION=1024x768.
Part B: stock kasmweb images
Section titled “Part B: stock kasmweb images”The SDK’s stock_image_mods directory adds the runtime to any kasmweb/* image. Every addition stays off until an environment variable turns it on, and the built image behaves like the stock one by default.
1. Build the layer
Section titled “1. Build the layer”Dockerfile.chrome and Dockerfile.libre-office are ready to build. They differ only in BASE_IMAGE. From the stock_image_mods directory:
docker build -t kasmweb/chrome:stock-mods -f Dockerfile.chrome .The build downloads the released runtime for the image’s architecture (amd64 or arm64), checks its SHA-256 and installs it with the bundled sidebar. No registry login is needed. The image then starts the runtime with the session when KASM_RUNTIME_SESSION_TOKEN is set, honours KASM_RUNTIME_SIDEBAR, ACCESSIBILITY_ENABLE and the KVNC_* screen settings, and otherwise behaves as the stock image. The Dockerfile pins the same release as the nix images; the build arguments at its top select another.
For another kasmweb/* image, copy Dockerfile.chrome and change BASE_IMAGE.
2. Add it to Kasm
Section titled “2. Add it to Kasm”Push the image to your registry and add it as a workspace . Then register the port map and set the environment exactly as in part A, steps 1 and 2.
Stock images ignore VNC_RESOLUTION, and the screen stays at 1024x768 whatever Kasm sets. Use the KVNC_* variables in the environment map:
| Variable | Effect | Unset |
|---|---|---|
KVNC_DESKTOP_RESOLUTION_WIDTH, KVNC_DESKTOP_RESOLUTION_HEIGHT |
Screen size | 1024x768 |
KVNC_DESKTOP_ALLOW_RESIZE=false |
An attached viewer can no longer resize the desktop. Set it for agent runs | Resize allowed |
ACCESSIBILITY_ENABLE=1 |
Sets the accessibility switch before the app starts. LibreOffice on the stock image publishes its full accessibility tree without it | Switch stays off |
One token per session
Section titled “One token per session”To give every session its own token, set KASM_RUNTIME_SESSION_TOKEN in the environment map of the Kasm API’s request_kasm call instead of on the workspace. Kasm keeps that map only when the call is authenticated with an API key. With a username and password the variables are dropped and no error is returned.
The SDK’s provisioning helper sends the map with an API key. From the SDK’s examples directory:
TOKEN=$(python3 -c 'import secrets; print(secrets.token_hex(16))')eval "$(uv run lib/kasm_provision.py up --ref <image> --env KASM_RUNTIME_SESSION_TOKEN="$TOKEN")"Drive a session from Claude Code uses this.
Check it works
Section titled “Check it works”Start a session of the workspace.
From outside the session
Section titled “From outside the session”The endpoint is https://<kasm host>/desktop/<kasm_id>/mcp/mcp. The first mcp is the port map’s name and the second is the runtime’s own path. A request carries Kasm’s session cookie and the token header. The SDK prints both, with lines to paste into Claude Code and Codex:
uv run lib/kasm_provision.py mcp-connect --kasm-id <kasm_id> --token <your token>Then send an MCP initialize request:
curl -s -X POST "https://<kasm host>/desktop/<kasm_id>/mcp/mcp" \ -H "X-Kasm-Runtime-Session-Token: <your token>" \ -H "Cookie: session_token=<cookie>; username=<user>" \ -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'The reply names serverInfo.name as kasm-session-runtime. A wrong token gets HTTP 401.
From inside the session
Section titled “From inside the session”From a terminal in the session, send the same request to http://127.0.0.1:9434/mcp with only the token header. On a stock image, the runtime’s log is /tmp/kasm-session-runtime-mcp.log, and a line containing serving N methods over HTTP on 0.0.0.0:9434 means it started.
The sidebar
Section titled “The sidebar”With KASM_RUNTIME_SIDEBAR=default, the viewer shows a panel to the right of the desktop with three tabs: Home, Timeline and Inbox. Home shows the session’s mode and its CDP and vision configuration.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| HTTP 401 | The X-Kasm-Runtime-Session-Token header is missing or does not match the session’s token |
Send the value the session was started with, in that header. Kasm’s proxy overwrites Authorization with the literal string None before the request reaches the runtime |
| HTTP 502 | The runtime is not up yet, or the workspace has no mcp port map |
Wait a few seconds and retry. If it persists, check the port map, and that the session was started after you added it |
| The token variables never reach the session | The session was requested through the API with a username and password | Request it with an API key |
| No sidebar | KASM_RUNTIME_SIDEBAR is unset, malformed, or names a sidebar the image does not have |
Set it to default. An unknown name is logged with the installed names and gives the stock viewer |
| Stock image stays 1024x768 | Stock images ignore VNC_RESOLUTION |
Use KVNC_DESKTOP_RESOLUTION_WIDTH and _HEIGHT on an image built with the layer |
dom.* tools cannot reach the browser |
Chrome was started without a debugging port, or without --user-data-dir |
Set APP_ARGS as above. Flags apply only at Chrome’s first launch |