# Coding agents (MCP)

Connect Claude Code, Codex, Cursor or any MCP client to Kaabist and let it build, test and maintain programs in a real Kaabist browser.

Kaabist runs a hosted [Model Context Protocol](https://modelcontextprotocol.io) server at
`https://mcp.kaab.ist/mcp` that lets a coding agent build programs for you in a **real Kaabist
browser** on Kaabist's pool, not its own. The agent clicks, fills and reads a condensed page view
with ranked locator candidates, tries extracts on the live page, turns what it did into a draft
program, lints and tests it, and follows jobs and healing proposals afterwards.

## When to use it

Connect an agent when you already work with one (Claude Code, Codex, Cursor or any MCP client) and
want it to write and maintain programs: describing a site in a sentence and reviewing the program
it produces is often faster than picking every field by hand. The agent works with your role and
within limits you set, and everything it does is visible in the panel.

Prefer the [program builder](https://docs.kaab.ist/panel/builder) when you want to pick fields yourself, and the
[SDKs](https://docs.kaab.ist/guides/sdks) when your own code runs programs in production. An agent is a way to author
programs, not a runtime.

## Connect

**Claude Code**

```bash
claude mcp add --transport http kaab https://mcp.kaab.ist/mcp
```

**Codex**, in `~/.codex/config.toml`:

```toml
[mcp_servers.kaab]
url = "https://mcp.kaab.ist/mcp"
```

**Cursor**, in `.cursor/mcp.json`:

```json
{ "mcpServers": { "kaab": { "url": "https://mcp.kaab.ist/mcp" } } }
```

The first time the agent connects, your browser opens Kaabist's consent page.

## Consent

The consent page opens in the panel (sign in first if you are not). It is a single card that names
your agent ("… wants to use Kaabist"), with the address it will send you back to underneath,
and four choices:

- **Organisation**: the agent works in one organisation (your current one is preselected), with
  **your** role in it. It can never do more than you can, and if you leave the organisation it stops
  at once.
- **Mode**: **Test** (the default: free test jobs on a small daily quota, results kept 24 hours) or
  **Live** (real jobs that spend credits). Choosing Live shows a warning that the agent will spend
  credits and act on real sites with your organisation's sessions.
- **Allow the agent to**: one checkbox per scope.

  | Checkbox | Scope | Lets the agent | Ticked by default |
  |---|---|---|---|
  | Read | `kaab:read` | read programs, jobs, proposals, sessions, usage and your credit balance | yes |
  | Browse | `kaab:browse` | open Kaabist browsers you can watch and take over | yes |
  | Run | `kaab:run` | save draft programs and run test jobs | yes |
  | Publish | `kaab:publish` | publish program versions and move aliases | no |
  | Approve | `kaab:approve` | approve fixes and make them permanent | no |
  | Side effects | `kaab:side_effects` | let test runs perform steps marked as side effects (orders, sends, deletes) | no |

- **Credits per agent session**: the most one agent session may spend (100 by default, at most
  10 000).

**Allow** sends you back to the agent; **Deny** refuses. Where two-factor authentication is
required, the page asks you to enrol an authenticator first. A grant never manages keys, members,
billing, webhooks or BYO providers, and never takes over a job.

**Settings → Connected apps** lists every agent you connected in a table (app, organisation, mode,
scopes, when it connected, when it was last used) and, above it, the agent browsers open now.
**Revoke** asks for confirmation and stops the agent's tokens at once.

## Credits

Browser time is billed while a browser is open, in test mode too, because test mode's free quota
covers jobs, not browsers. Each session stops at its cap. The `estimate` tool tells the agent what
a job and a browser minute cost; `credits` shows your balance and what the session spent.

## Watching and taking over

Every browser an agent opens appears in the panel's top bar, under the **N live** button that lists
open browsers as "Agent browser", and on **Connected apps**. Open it to watch: the page shows the
browser on the left with its address and connection status, and the banner "Agent is driving" with the line "You
are watching". A rail on the right shows what has been recorded so far, with **Take control** and
**Close the browser**. **Take control** hands the browser to you; the agent's browser tools then
answer `controlled_by_person` until you press **Return control**. What you do while in control is
recorded like the agent's actions.

## Without OAuth

An agent that cannot run the OAuth flow can send an API key instead:
`Authorization: Bearer sk_test_…`. The key's own mode and scopes apply (`read` reads; `run` also
browses, runs, publishes and approves; `admin` adds side effects), with a 100-credit cap per
session.

## Tools

| Tool | Does |
|---|---|
| `browser_open` | open a Kaabist browser on a URL; returns its `browser_id`, the watch link and a page view |
| `browser_navigate`, `browser_click`, `browser_fill`, `browser_select`, `browser_scroll`, `browser_press`, `browser_wait` | act on the page (targets are page-view refs like `e12` or DSL targets); each answers what it did (`effects`) and what still covers the page (`obstacles_after`) |
| `browser_page_view` | the condensed page: obstacles, actions, outline, landmarks, forms, lists, text elements, JSON paths |
| `browser_screenshot` | a JPEG of the viewport, or of a whole long page as tiles |
| `browser_evaluate`, `browser_download` | run JavaScript in the page, or download a file into the browser's artifacts; both recorded as steps |
| `browser_close` | close the browser and settle what it cost |
| `browser_to_program` | turn what was done into a draft program |
| `try_extract` | run an extract on the live page (or on a URL, as a job) |
| `lint_program`, `push_program`, `diff_versions` | check, save (a draft unless allowed to publish) and compare versions |
| `test_program` | run a version as a job: a dry run unless told otherwise |
| `answer_input` | answer a job waiting for input (a one-time code) |
| `get_job`, `list_jobs`, `cancel_job`, `resume_job` | follow jobs, cancel them and act on parked ones; a resume within minutes of the park carries on in the same page and keeps the data and files from before it |
| `list_proposals`, `get_proposal` | read healing proposals |
| `get_artifact` | a file a job or a browser kept: a link, and the file itself up to 1 MB |
| `estimate`, `credits` | prices and balance |

Resources: `kaab://docs/dsl-quickref`, `kaab://schemas/program-1.json`, `kaab://programs/{name}`.
Prompts: `build_program_for_site` (a `url` and a `goal`) and `fix_failing_job` (a `job_id`).

## Reading a page

A page view leads with what an agent needs before it acts:

- **`obstacles`**: what covers the page, such as a consent banner, a dialog or a tour. Each says its
  `kind`, the `vendor` when Kaabist recognises the framework, whether it `covers` the whole
  viewport or part of it, and up to three `dismiss` controls (`reject` first, then `close`, then
  `accept`) by ref. Kaabist never dismisses anything for the agent; clicking one of those refs is
  recorded like any other click.
- **`actions`**: every visible link and button, in page order, with where it sits (`in`: `header`,
  `nav`, `main`, `footer`, `dialog`) and up to three locator candidates. A button repeated with the
  same text and link is listed once with a `count`.
- Then the `outline` (headings h1 to h3), `landmarks`, `forms` (their fields, never their values),
  repeating `lists` with sample texts, other text `elements`, and the paths found in
  `embedded_json` (such as `ld+json` blocks) and `network_json` (JSON responses the page loaded).

A view never exceeds its byte budget (`max_bytes`, 20 000 by default); `truncated` names the
sections that were shortened, the text elements first, the obstacles last. Refs such as `e12` are
valid until the next page view.

Every action answers `changed` and `effects` (whether it navigated, added or removed nodes,
scrolled, cleared an obstacle, or opened or closed a dialog), so the agent rarely needs a screenshot
to know what happened. A long page is read with `browser_scroll { by: "page" }` until `at_end`
(these relative scrolls are not recorded), and seen with `browser_screenshot { full_page: true }`:
the page is revealed first, then returned as tiles up to 1 600 px tall, six per call, continued with
`from_tile`.

## Worked example

Take the home page of `https://app.example.com`, a long marketing page: a sticky header, 45
headings, two large `ld+json` blocks, a carousel, lazy images and a Cookiebot consent banner over
the bottom of the screen. Ask your agent: *"Open https://app.example.com/ and find where Start
building leads."*

**The page view.** `browser_open` answers with a view whose title is "Example Cloud: cloud runtime
for AI agents" and whose `obstacles` holds one entry: `kind: "consent"`, `vendor: "cookiebot"`,
`covers: "partial"`, with a `reject` control labelled "Reject all". Its ref also appears in
`actions` with `in: "dialog"`, next to "Show details". The other actions read in page order: the
header's links (Docs, Pricing, Blog and Changelog `in: "nav"`, Sign in), then "Start building"
`in: "main"` with `count: 2` (the page shows it twice), "Talk to an engineer", the carousel's
"Previous slide" and "Next slide" buttons, and the footer's links. The outline keeps 40 of the 45
headings, every h1 and h2 and the first h3s, and the `ld+json` paths are cut to a sample; both are
named in `truncated`, so the agent knows there is more.

**Acting on it.** The agent clicks the "Reject all" ref. The answer says `obstacle_cleared: true`
and `obstacles_after` is empty. It scrolls by page until `at_end` to read every heading, then clicks
"Start building": `effects.navigated` is true and the URL ends in `/start`. A click on the
carousel's "Next slide" would instead answer `changed: true` with `navigated: false`, because the
slides move inside the page.

**Building a program.** On a page with data, such as `https://shop.example.com/p/1`, the
agent continues with `try_extract` to check fields against the live page, `browser_to_program` to
turn its steps into a draft, `lint_program`, `push_program` and `test_program`, which runs the
draft as a dry run. You see the browser in the panel's top bar the whole time, and the draft under
**Programs**.

To see which tools a bearer may call without an MCP client, open a session and list them. The
list depends on the scopes: a `read` key gets the reading tools only.

### TypeScript

```ts
const headers = {
  authorization: `Bearer ${apiKey}`, // an sk_ key, or an OAuth access token
  "content-type": "application/json",
  accept: "application/json, text/event-stream",
};
const rpc = (id: number, method: string, params: object = {}) =>
  JSON.stringify({ jsonrpc: "2.0", id, method, params });

// 1. initialize opens a session; its id comes back in Mcp-Session-Id.
const init = await fetch(endpoint, {
  method: "POST",
  headers,
  body: rpc(1, "initialize", {
    protocolVersion: "2025-11-25",
    capabilities: {},
    clientInfo: { name: "kaab-example", version: "1.0.0" },
  }),
});
const session = init.headers.get("mcp-session-id") ?? "";
const hello = await readMessage(init);
console.log(`connected to ${hello.result?.serverInfo?.name}`);

const sessionHeaders = { ...headers, "mcp-session-id": session };
await fetch(endpoint, {
  method: "POST",
  headers: sessionHeaders,
  body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
});

// 2. tools/list: only the tools this bearer's scopes allow.
const listed = await readMessage(
  await fetch(endpoint, { method: "POST", headers: sessionHeaders, body: rpc(2, "tools/list") }),
);
for (const tool of listed.result?.tools ?? []) console.log(`${tool.name}`);
```

## Pitfalls

- **Live mode by accident.** Choose **Test** on the consent page unless the agent must do real
  work; live browsers and jobs spend real credits.
- **Giving `kaab:side_effects` to explore.** A test run with it places real orders and sends real
  messages. Leave it off until a program's side effects are what you want.
- **Expecting the agent to dismiss banners on its own.** Kaabist reports obstacles and their
  controls; the agent has to click one.
- **Stale refs.** A ref is valid until the next page view; after a navigation, read the page again.
- **Hitting the credit cap mid-task.** The session stops at its cap. Raise it on the consent page
  when you reconnect, or close browsers the agent no longer needs.

## Reference

- [Create a job](https://docs.kaab.ist/reference/api/jobs/create-job), [lint a program](https://docs.kaab.ist/reference/api/programs/lint-program)
- [Key scopes](https://docs.kaab.ist/reference/codes#key-scopes) and [error codes](https://docs.kaab.ist/reference/codes#errors)
- [DSL reference](https://docs.kaab.ist/reference/dsl)
