# Tools

Source: https://webagent-docs-staging.lookover.ai/mcp/tools

> Reference for every webagent MCP tool, its arguments, what it returns and when it reports an error.

The [MCP server](/mcp) offers 15 fixed tools and one `skill_<name>` tool per skill. This page lists each one. Your client also shows the exact JSON Schema of every tool in `tools/list`, which is the final word on argument names.

Every tool call runs with your API key, so the same limits, plans and quotas apply as on the REST API. The tools call the same services as the REST routes.

| Tool | What it does | Waits? |
|---|---|---|
| [`create_session`](#create_session) | Open a cloud browser session | No |
| [`run_task`](#run_task) | Give the browser agent a goal and wait for the answer | Up to 90 s |
| [`get_task`](#get_task) | Read a task, or wait for a running one | Up to 90 s |
| [`smoke_test`](#smoke_test) | Test a page or flow and return a pass/fail report | Up to 90 s (60 s by default) |
| [`get_report`](#get_report) | Read the test report of a run or task, or wait for it | Up to 90 s (30 s by default) |
| [`list_sessions`](#list_sessions) | List the project's sessions | No |
| [`get_session`](#get_session) | Read one session | No |
| [`stop_session`](#stop_session) | End a session | No |
| [`scrape`](#scrape) | Load a URL and return its content | Until done |
| [`screenshot`](#screenshot) | Load a URL and return an image | Until done |
| [`pdf`](#pdf) | Load a URL and return a PDF | Until done |
| [`crawl`](#crawl) | Crawl from a URL and return the pages | Until done |
| [`list_skills`](#list_skills) | List the skills you can run | No |
| [`run_skill`](#run_skill) | Start a skill run, without waiting | No |
| [`get_skill_run`](#get_skill_run) | Read a skill run, or wait for its verdict | Up to 120 s |
| [`skill_<name>`](#skill_name) | Run one skill and wait for its verdict | 30 s by default, up to 120 s |

Ids look like `sess_us_…` for sessions, `tsk_…` for tasks and `run_…` for skill runs.

## Results and errors [#results-and-errors]

Most tools return two things: `content` (text for the model) and `structuredContent` (the same data as JSON). A few return an image or a PDF instead.

A call that fails returns normally with `isError: true`. The text is `[code] message`. `structuredContent.error` holds `{ code, message, details? }`. The codes are the [API error codes](/reference/errors). Argument problems come back as `invalid_input`, with the validation issues in `details.issues`. An unknown tool name is `not_found`.

> **Warning: A failed test is a result, not an error**
>
> `isError` means "the test could not run", not "the app has a problem". A test that ran and found a problem is a normal result. Read the verdict, not `isError`. See [Reading results](/qa-testing/reading-results).

How each tool sets `isError`:

| Tool | `isError: true` when |
|---|---|
| `smoke_test`, `get_report` | The report verdict is `error`: the test could not run to a result. Verdict `fail` is a normal result |
| `run_task`, `get_task` | The call failed, or the task ended in a way that means the agent could not test: `stoppedBy` is `captcha`, `blocked`, `error`, `aborted`, `paused`, `navigation_blocked`, `capacity_exceeded`, `capacity_timeout` or `timeout`. A task that ended `stuck`, `budget` or `unanswered`, or finished with `ok: false`, is a normal result |
| `skill_<name>`, `get_skill_run` | The call failed, or the run `status` is `error`, `denied` or `cancelled`. A run that is `failed` is a normal result. A run still `running` is not an error |
| every other tool | The call failed |

---

### `create_session` [#create_session]

Opens a cloud browser session, like `POST /v1/sessions`. Use it when several tasks must share one browser, or when you want to keep the session open and watch it. Then pass the returned id as `sessionId` to `run_task`. For a single task, `run_task` takes the common options itself (recording, headers, proxy, vault login).

| Argument | Type | Required | Notes |
|---|---|---|---|
| `startUrl` | string | no | `http` or `https`, at most 2048 characters. The browser opens it before the session is live. A blocked address fails with `navigation_blocked` |
| `device` | string | no | `desktop`, `mobile` or a device id from the device list |
| `viewport` | object | no | `width` and `height` in pixels (200 to 7680 wide, 200 to 4320 high), optional `deviceScaleFactor` and `mobile` |
| `record` | boolean | no | Record the session as video. Kept after the session closes. See [Evidence](/concepts/evidence) |
| `headers` | array | no | Extra request headers sent only to the hosts you name. Up to 20 entries |
| `credentials` | boolean or object | no | Fill login forms from the credential vault |
| `proxyUrl`, `allowHosts` | string, string array | no | Reach a private host through your own proxy. `allowHosts` needs `proxyUrl` |
| `idleTimeoutMs` | integer | no | Close after this long with no actions. 10 000 ms to 24 h. Default 5 minutes |
| `maxDurationMs` | integer | no | Hard cap on the session's lifetime. 10 000 ms to 24 h. Default 1 hour |
| `engineId`, `engineVersion` | string, integer | no | Run on a saved engine. `engineVersion` needs `engineId` |
| `waitForCapacityMs` | integer | no | When every browser is busy, wait up to this long (0 to 30 000 ms) for a free one before failing with `capacity_exceeded`. Default 0 |
| `queueForCapacity` | object | no | `{ "maxWaitSeconds": 1 to 900 }`. When every browser is busy, keep the session queued instead of failing. See [Sessions](/concepts/sessions#when-no-browser-is-free). Not together with `waitForCapacityMs` above 0 |

The tool takes every field of the `POST /v1/sessions` body. The table lists the common ones. Do not send `url`: it is refused with a hint to use `startUrl`.

**Returns:** the session record as JSON, with its `id`, `status`, `endpoints` and a short-lived session `token`.

```json title="Arguments"
{ "startUrl": "https://staging.example.com", "record": true }
```

```json title="structuredContent (trimmed)"
{
  "id": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "live",
  "engine": "chromium",
  "createdAt": "2026-01-01T10:00:00.000Z",
  "token": "...",
  "tokenExpiresAt": "2026-01-01T10:15:00.000Z",
  "endpoints": { "http": "https://...", "cdp": "wss://...", "live": "wss://..." }
}
```

---

### `run_task` [#run_task]

Gives the browser agent a goal in plain language. The agent looks at the page, decides, acts, and repeats until it is done or stops. The tool returns the answer, why it stopped, the steps and the cost.

Without `sessionId`, the tool opens a session itself and closes it when the task ends, unless `keepOpen` is `true`. With `sessionId`, the task runs on that open session, and the session stays open.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `goal` | string | yes | What to do, 1 to 2000 characters |
| `maxSteps` | integer | no | 1 to 50. The agent's default is 25 |
| `sessionId` | string | no | An open session of your project. Leave it out to let the tool open one |
| `startUrl` | string | no | Page to open first. Only when the tool opens the session |
| `engineId`, `engineVersion` | string, integer | no | Engine for the new session. Only when the tool opens the session |
| `record` | boolean | no | Record the session as video. Only when the tool opens the session. The report then links the recording |
| `headers` | array | no | Extra request headers for the hosts you name, for a staging site behind a gate. Same rules as in `create_session`. Only when the tool opens the session. See [Protected sites](/guides/protected-sites) |
| `proxyUrl`, `allowHosts` | string, string array | no | Reach a private host through your own proxy. `allowHosts` needs `proxyUrl`. Only when the tool opens the session. See [Test a local app](/guides/local-apps) |
| `credentials` | boolean or object | no | Fill login forms from the credential vault: `true`, or `{ namespace, autoSubmit }`. Only when the tool opens the session. There is no field for a plain password. See [Signed-in sites](/guides/signed-in-sites) |
| `mode` | `auto` or `full_auto` | no | Default `auto`. See [Tasks](/concepts/tasks#modes) |
| `keepOpen` | boolean | no | Keep the session open after the task, when the tool opened it |
| `waitSeconds` | integer | no | 1 to 90. How long to wait. Default 90. A value above 90 is refused with `invalid_input` |

Giving `startUrl`, `engineId`, `engineVersion`, `record`, `headers`, `proxyUrl`, `allowHosts` or `credentials` together with `sessionId` is an `invalid_input` error. These options only apply when `run_task` opens the session.

**Returns, when the task ends within the wait:**

- Text: `Task finished|stopped (<stoppedBy>) in N steps, $x, S s.` then `Answer:`, `Reason:`, a line that says whether the session was closed, and a line that names [`get_report`](#get_report) with the task id.
- `structuredContent`: `{ sessionId, sessionClosed, task }`. `task` has `taskId`, `ok`, `answer`, `stoppedBy`, `reason`, `limit`, `steps`, `costMicros`, `ms` and `text`. Each step has `op`, `ref`, `provider`, `ok`, `ms`, `costMicros` and `error`. Costs are in millionths of a US dollar. Long strings are cut at 2000 characters.

**Returns, when the wait ends first:** the task keeps running and its session stays open. The result is `{ sessionId, taskId, status: "running", sessionClosed: false, note }`. Call [`get_report`](#get_report) with the `taskId` (or [`get_task`](#get_task)), then [`stop_session`](#stop_session) when you are done. A task that runs longer than the wait is normal.

If the call fails before a result exists (for example `session_paused`), a session the tool opened is closed and the call returns `isError`. A task that ended because the agent could not test (a CAPTCHA, a blocked address and so on) also returns `isError`. See [Results and errors](#results-and-errors).

`run_task` is for an open-ended goal. It checks no assertions: `ok: true` means the agent says it is done. For a repeatable pass/fail check of a page or flow, use [`smoke_test`](#smoke_test).

```json title="Arguments"
{
  "goal": "Open the first product, add it to the cart and report the cart total",
  "startUrl": "https://staging.example.com",
  "maxSteps": 20,
  "keepOpen": true
}
```

```json title="structuredContent (trimmed)"
{
  "sessionId": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx",
  "sessionClosed": false,
  "task": {
    "taskId": "tsk_xxxxxxxxxxxxxxxxxxxxxxxx",
    "ok": true,
    "answer": "The cart total is 24.00.",
    "stoppedBy": "done",
    "steps": [{ "op": "click", "ok": true, "ms": 410 }],
    "costMicros": 8200,
    "ms": 24000
  }
}
```

The text for the same call reads `Task finished (done) in 2 steps, $0.0006, 5 s.`, then `Answer: ...`, then `Session sess_us_... is still open: run another task with sessionId, or stop_session when done.`

> **Note: Do not put real passwords in a goal**
>
> The goal text goes to the model and into the task record. Use the credential vault for logins. See [Signed-in sites](/guides/signed-in-sites).

---

### `get_task` [#get_task]

Reads a task started by `run_task`. It waits for a running task, then returns the result. It never closes the session.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `sessionId` | string | yes | The session the task ran on |
| `taskId` | string | no | Default: the session's latest task |
| `waitSeconds` | integer | no | At least 0. Default 30, maximum 90. A larger value is lowered to 90 |

**Returns, when the task is finished:** text with the same summary as `run_task`, and `structuredContent` `{ sessionId, taskId, status: "finished", task }`. Here `task` is the stored task record: `id`, `goal`, `status`, `ok`, `stoppedBy`, `limit`, `reason`, `answer`, `steps`, `costMicros`, `ms`, `startedAt`, `finishedAt`.

**Returns, when it still runs:** `{ sessionId, taskId, status: "running", note }`. Call again, or use [`get_report`](#get_report) with the `taskId`.

`get_task` returns the raw task record. For a verdict with console errors and failed network calls, use [`get_report`](#get_report).

A session that has no task yet, or a `taskId` that is not on the session, is `not_found`.

```json title="Arguments"
{ "sessionId": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx", "waitSeconds": 60 }
```

```json title="structuredContent, finished (trimmed)"
{
  "sessionId": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx",
  "taskId": "tsk_us_xxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "finished",
  "task": {
    "id": "tsk_us_xxxxxxxxxxxxxxxxxxxxxxxx",
    "goal": "Open the Pricing page and tell me the page heading",
    "status": "finished",
    "ok": true,
    "stoppedBy": "done",
    "answer": "The Pricing page is open. Its page heading is \"Pricing\".",
    "steps": [{ "op": "click", "ref": "e2", "ok": true, "ms": 3458, "costMicros": 472 }],
    "costMicros": 617,
    "ms": 4753,
    "startedAt": "2026-01-01T10:00:06.000Z",
    "finishedAt": "2026-01-01T10:00:11.000Z"
  }
}
```

---

### `smoke_test` [#smoke_test]

Tests a page or a short user flow and returns a report. This is the tool for "smoke test my app". One call opens a recorded cloud browser, loads the page, runs your plain-language steps, checks what you expect, and returns a verdict. It runs the `smoke-uat` skill and always records the session.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `url` | string | yes | The page to test. `http` or `https`, at most 2048 characters |
| `steps` | string array | no | Plain-language steps run in order after the page loads, for example `["click Sign in", "type the username demo into the email field"]`. Up to 30 steps of up to 500 characters. The first step that fails stops the flow. Plain steps need the browser agent to read the page. The step text is stored with the run: use a throwaway test account |
| `expectText` | string | no | Text that must be on the page when the steps are done. Up to 500 characters. Case-sensitive |
| `expectUrl` | string | no | Text the page address must contain when the steps are done, for example `/dashboard`. Up to 500 characters |
| `expectNoText` | string | no | Text that must not be on the page when the steps are done, for example `Invalid password`. Up to 500 characters |
| `maxLinks` | integer | no | How many same-origin links of the start page to check. 0 to 200. Default 50 |
| `strictLinks` | boolean | no | A broken link fails the test. Default `true`. With `false` it is a finding only |
| `waitSeconds` | integer | no | How long to wait for the result. 1 to 90. Default 60 |
| `session` | object | no | Session options for this test, for example `proxyUrl` and `allowHosts` to reach a private staging site, or `headers` for a gate. Recording is always on. Sign-in options (`credentials`, `profileId`, `persistProfile`, `profileName`, `extensionIds`) are refused with `invalid_input`: skill runs cannot sign in yet |

The test checks, in this order: the start page loads, basic accessibility, the links of the start page, your steps, the console, then `expectText`, `expectUrl` and `expectNoText`, and last whether every link worked (`strictLinks`). Links and accessibility are checked on the start page only, not on pages your steps reach. The console check covers the whole run. A failed step does not skip the later checks, so you see a broken link and a failed flow in one report. See [Skills](/concepts/skills#smoke-uat).

**Returns:** the report of the run. The text has the summary, up to five failed steps (with a screenshot link when there is one) and a short-lived link to the full HTML report. `structuredContent` is the whole [report](/qa-testing/reading-results#the-test-report). It has a `verdict`: `pass`, `fail`, `error` or `running`.

- `isError` is `true` only for verdict `error`: the test could not run to a result, for example the run was cancelled, was denied a permission, or the browser failed. A test that ran and failed is a normal result with verdict `fail`.
- If the test is still going when the wait ends, the verdict is `running` and the summary says to call [`get_report`](#get_report) with the `id`.

```json title="Arguments"
{
  "url": "https://staging.example.com/login",
  "steps": ["type the username qa@example.com into the email field", "type the test password into the password field", "click Sign in"],
  "expectText": "Welcome",
  "expectNoText": "Invalid password",
  "expectUrl": "/dashboard"
}
```

The text for a failed test reads like this. It is from a run on the Test Arena sign-in page where `expectText` named a text that is not there (links shortened):

```text
Smoke test of https://webagent-test-web.pages.dev/app/login FAILED in 7 s: 15 of 16 steps passed. Step 13 (assert 1 condition) failed: Expected text "Dashboard overview" is not on the page. No console errors or failed network calls.
Failed steps:
- Step 13 (assert 1 condition): Expected text "Dashboard overview" is not on the page (screenshot: https://...)
Full HTML report (short-lived link): https://...
The whole report is in structuredContent.
```

A test that passes has a shorter text: `Smoke test of https://webagent-test-web.pages.dev/app/login PASSED in 5 s: 17 of 19 steps passed. No console errors or failed network calls.`, then the report link line and the last line. Steps for checks you did not ask for (here `expectUrl` and `expectNoText`) are `skipped`: they are not counted in the first number, so it can be lower than the second. Step numbers in the text start at 1, the same as in the HTML report; a step inside a loop reads like `Step 3.2`. In `structuredContent` the `index` of a step starts at 0.

---

### `get_report` [#get_report]

Reads the test report of a smoke test, a skill run or a task. It waits while the test is still running, so call it again with the same id until the verdict is not `running`.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `id` | string | yes | A run id (`run_…`) from `smoke_test` or `run_skill`, or a task id (`tsk_…`) from `run_task` |
| `waitSeconds` | integer | no | How long to wait while it is still running. 0 to 90. Default 30 |

**Returns:** the same as [`smoke_test`](#smoke_test): text with the summary and failed steps, and the whole report in `structuredContent`. `isError` is `true` only for verdict `error`. An id that does not exist in your project is `not_found`.

A task report says in its `notes` that a task has no assertions: verdict `pass` means the agent says it is done. A report of a running test has few details: steps and findings appear when it ends.

The text of a task report reads like this:

```text
Task "Open the Pricing page and tell me the page heading" finished in 3 s: the agent says it is done. 2 agent steps, all ok. Answer: The Pricing page is open at https://webagent-test-web.pages.dev/pricing/. The page heading is "Pricing". No console errors or failed network calls. Note: no assertions were checked, so `ok` only means the agent says it is done.
The whole report is in structuredContent.
```

Console errors and failed network calls come from the session log. The log is saved when the session ends. A report read while the session is still open has empty lists and no `finalUrl`, and says in `notes` that the log is not available: "The session log is not available (it is stored when the session ends), so console errors, failed network calls and URLs are missing." Call [`stop_session`](#stop_session), then read the report again: the same report then has the log data. A task that left its session open (`keepOpen`, or still running) has no log until you do. `smoke_test` and a `run_task` that closes its own session return after the log is saved, so their report is complete at once.

```json title="Arguments"
{ "id": "run_us_xxxxxxxxxxxxxxxxxxxxxxxx", "waitSeconds": 60 }
```

For the raw record of a run or task, use [`get_skill_run`](#get_skill_run) or [`get_task`](#get_task). The report fields are in [Reading results](/qa-testing/reading-results#the-test-report). REST: `GET /v1/skill-runs/:id/report.json` and `GET /v1/tasks/:id/report.json` return the same report.

---

### `list_sessions` [#list_sessions]

Lists the project's sessions, newest first (at most 200). No arguments.

**Returns:** `{ "sessions": [ … ] }`. Each entry is a session record: `id`, `status`, `engine`, `createdAt`, `endedAt`, `endReason`, `title`.

```json title="Arguments"
{}
```

---

### `get_session` [#get_session]

Reads one session: status, regions and pool. While the session is open, the record also has its `endpoints`.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `sessionId` | string | yes | The session id |
| `id` | string | no | Deprecated alias of `sessionId`. Send one of them, or both with the same value |

**Returns:** the session record as JSON. An unknown id, or one from another project, is `not_found`.

```json title="Arguments"
{ "sessionId": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx" }
```

---

### `stop_session` [#stop_session]

Stops a session and ends its record. Safe to repeat.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `sessionId` | string | yes | The session id |
| `id` | string | no | Deprecated alias of `sessionId` |

**Returns:** `{ "deleted": true, "closed": true }`. `closed` is `true` when this call stopped a running browser. Repeating the call on a session that already ended returns `{ "deleted": true, "closed": false }`. `deleted` is `false` when the id is unknown or not yours.

```json title="Arguments"
{ "sessionId": "sess_us_xxxxxxxxxxxxxxxxxxxxxxxx" }
```

---

### `scrape` [#scrape]

Loads a URL in a cloud browser and returns the page content. Each call opens its own fresh session, so it never sees a page you are signed in to. See [Sessions](/concepts/sessions).

| Argument | Type | Required | Notes |
|---|---|---|---|
| `url` | string | yes | The page |
| `format` | array | no | Any of `html`, `cleaned_html`, `markdown`, `readability`. Default `["markdown"]` |
| `waitUntil` | string | no | `domcontentloaded`, `load` or `networkidle`. Default `load` |
| `waitForText` | string | no | Wait until this text is on the page. At most 500 characters |
| `timeoutMs` | integer | no | 1000 to 120 000. Default 30 000 |
| `screenshot`, `pdf`, `mhtml` | boolean | no | Also return the page as base64 inside the JSON |
| `inventory` | boolean | no | Also list embedded media and third-party hosts |
| `engine`, `device`, `viewport`, `block`, `allowHosts`, `proxyUrl` | | no | Same meaning as in `create_session` |

**Returns:** JSON as text and `structuredContent`: `url`, `content` (one key per requested format), `metadata` (title, status code, description, word count), `links`.

```json title="Arguments"
{ "url": "https://webagent-test-web.pages.dev", "format": ["markdown"] }
```

```json title="structuredContent (trimmed)"
{
  "url": "https://webagent-test-web.pages.dev/",
  "content": { "markdown": "[Test Arena](/)\n...\n# A practice site for browser agents\n..." },
  "metadata": {
    "title": "Test Arena · a practice site for browser agents",
    "statusCode": 200,
    "language": "en",
    "wordCount": 190
  },
  "links": [{ "url": "https://webagent-test-web.pages.dev/pricing/", "text": "Pricing", "internal": true }]
}
```

---

### `screenshot` [#screenshot]

Loads a URL and returns an image. The result is an MCP image content item, with no `structuredContent`.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `url` | string | yes | The page |
| `fullPage` | boolean | no | Default `true` |
| `format` | string | no | `png`, `jpeg` or `webp`. Default `png` |
| `quality` | integer | no | 0 to 100 |
| `waitUntil`, `waitForText`, `timeoutMs` | | no | As in `scrape` |
| `engine`, `device`, `viewport`, `block`, `allowHosts`, `proxyUrl` | | no | As in `create_session` |

```json title="Arguments"
{ "url": "https://example.com", "fullPage": false, "format": "jpeg", "quality": 80 }
```

This tool takes a screenshot of a fresh page. It cannot show the state a `run_task` left behind.

---

### `pdf` [#pdf]

Loads a URL and returns it as a PDF. The result is an MCP resource with `uri` `webagent://quick/pdf`, the MIME type `application/pdf` and the file as a base64 `blob`.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `url` | string | yes | The page |
| `paper` | string | no | `A4`, `Letter` or `Legal`. Default `A4` |
| `landscape` | boolean | no | Default `false` |
| `printBackground` | boolean | no | Default `true` |
| `waitUntil`, `waitForText`, `timeoutMs` | | no | As in `scrape` |
| `engine`, `device`, `viewport`, `block`, `allowHosts`, `proxyUrl` | | no | As in `create_session` |

```json title="Arguments"
{ "url": "https://example.com", "paper": "Letter" }
```

---

### `crawl` [#crawl]

Crawls from a URL and returns the pages it visited.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `url` | string | yes | Where to start |
| `scope` | string or array | no | `host` (default), `subdomains`, or a list of up to 20 hosts |
| `include`, `exclude` | string array | no | URL patterns. Up to 50 each |
| `maxPages` | integer | no | 1 to 100. Default 25 |
| `maxDepth` | integer | no | 0 to 5. Default 2 |
| `delayMs` | integer | no | 0 to 10 000. Default 500 |
| `respectRobots` | boolean | no | Default `true` |
| `sitemap` | boolean | no | Default `false` |
| `content` | array | no | Also return page content, in these formats |
| `maxMs` | integer | no | Limit for the whole crawl. 1000 to 600 000. Default 300 000 |
| `cursor` | string | no | Continue a crawl that stopped. Use the `cursor` from the last result |

**Returns:** JSON with `start`, `pages` (each with `url`, `depth`, `status`, `title`, `linksFound`, `ms`), `skipped`, `skippedTotal`, `stoppedBy` (`complete`, `maxPages` or `timeout`), `pending` and, when the crawl stopped early, a `cursor`.

The crawl obeys the site's `robots.txt` unless you set `respectRobots` to `false`. A site that disallows everything returns no pages. Only crawl sites you own or may test.

```json title="Arguments"
{ "url": "https://staging.example.com", "maxPages": 10, "maxDepth": 1 }
```

---

### `list_skills` [#list_skills]

Lists the skills your project can run, with their inputs and permissions. No arguments. Each skill also has its own [`skill_<name>`](#skill_name) tool.

**Returns:** `{ "skills": [ … ] }`. See [Skills](/concepts/skills).

```json title="Arguments"
{}
```

---

### `run_skill` [#run_skill]

Starts a skill run by name and returns at once. It does not wait. Call [`get_report`](#get_report) with the `runId` for the pass/fail report, or [`get_skill_run`](#get_skill_run) for the raw record. To test a page or flow, [`smoke_test`](#smoke_test) is easier, because it waits and returns the report. The typed [`skill_<name>`](#skill_name) tool also waits.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | The skill name, for example `smoke-uat` |
| `inputs` | object | no | The skill's inputs. Default `{}` |
| `version` | string | no | A skill version. Default: the newest |
| `grant` | array | no | Permissions to grant, from `network_capture`, `active_scan`, `file_access`. A skill that does active security probes needs `["active_scan"]`. Ask the user first |
| `session` | object | no | Session options for the run, for example `proxyUrl` and `allowHosts` |

`engineId` picks the engine for the run's session, as on the REST route.

**Returns:** `{ "runId": "run_…", "sessionId": "sess_us_…", "status": "running", "placement": "queued" }`.

```json title="Arguments"
{ "name": "smoke-uat", "inputs": { "url": "https://staging.example.com" } }
```

---

### `get_skill_run` [#get_skill_run]

Reads a skill run. While it runs, the tool waits up to `waitSeconds` for the verdict.

| Argument | Type | Required | Notes |
|---|---|---|---|
| `runId` | string | yes | The run id |
| `waitSeconds` | integer | no | 0 to 120. Default 0, which does not wait |

**Returns:** a short text summary and the run in `structuredContent`. See [the skill result](#the-skill-result) below. It sets `isError` only for status `error`, `denied` or `cancelled`. A `failed` run is a normal result: read `status`. For a verdict with failed steps, console errors and failed network calls, call [`get_report`](#get_report) with the `runId`.

```json title="Arguments"
{ "runId": "run_xxxxxxxxxxxxxxxxxxxxxxxx", "waitSeconds": 60 }
```

---

### `skill_<name>` [#skill_name]

One tool exists for each skill your project can see. A skill named `smoke-uat` becomes `skill_smoke_uat`: dashes turn into underscores. The tool starts the run, waits for the verdict and returns it with a report link.

The tool's description comes from the skill. Its arguments are the skill's own inputs, flat, plus two more:

| Argument | Type | Required | Notes |
|---|---|---|---|
| each skill input | the input's type | as the skill says | A `url` input is a string. A `string[]` input is an array of strings. A `secret` input takes a vault reference such as `https://host#password`, never the secret itself |
| `options.waitSeconds` | integer | no | 0 to 120. Default 30. A run still going when the wait ends comes back with `status: "running"` and a `runId` for `get_skill_run` |
| `options.session` | object | no | Session options for this run, for example `proxyUrl` and `allowHosts` to reach a private staging site |
| `confirm` | boolean | yes, for active scans only | Must be `true`. The tool has this argument only when the skill sends active security probes. Set it only after the user confirms they may test the target |

Skill runs cannot sign in yet. If `options.session` has `credentials`, `profileId`, `persistProfile`, `profileName` or `extensionIds`, the tool returns `isError` with `invalid_input` ("Signed-in skill runs are not available yet: remove session.credentials") and starts nothing. See [Skills](/concepts/skills#what-skills-cannot-do-yet).

**Returns:** the same as [`get_skill_run`](#get_skill_run): the raw run and a report link. The call sets `isError: true` only when the run could not run to a result: status `error`, `denied` or `cancelled`. A run that `failed` is a normal result, and so is one still `running`. To get a verdict with console errors and failed network calls, use [`smoke_test`](#smoke_test) or [`get_report`](#get_report) with the `runId`.

```json title="Arguments for skill_smoke_uat"
{
  "url": "https://staging.example.com",
  "steps": ["click Sign in", "type \"qa@example.com\" into Email"],
  "expectText": "Welcome",
  "options": { "waitSeconds": 60 }
}
```

#### The skill result [#the-skill-result]

The text of the result lists, in this order: the skill name, version, run id and status; the skill's summary; the reason for a stop, if there is one; the counts; up to five failed steps; and the report link.

`structuredContent` is the run: `runId`, `skill`, `sessionId`, `status`, `startedAt`, `ms`, `summary`, `counts`, `result`, `hasReport`, and `reportUrl` once a report exists. The statuses are `running`, `passed`, `failed`, `error`, `cancelled` and `denied`.

- Screenshots and video clips are left out of `structuredContent`. They are in the report.
- Strings longer than 2000 characters are cut, and only the last 50 log lines are kept.
- If the result is larger than 100 000 bytes, each step is reduced to one line: `index`, `path`, `kind`, `label`, `ok`, `ms` and `error`.
- `reportUrl` is a signed link that works without an API key. It stops working after a few minutes. See [Evidence](/concepts/evidence).

---

## Limits that apply to tools [#limits-that-apply-to-tools]

Waits are capped so that a single reply never stays silent for too long: 90 seconds for `run_task`, `get_task`, `smoke_test` and `get_report`, 120 seconds for the skill tools. A longer job keeps running, and you read it with `get_report`, `get_task` or `get_skill_run`. See [Limits](/concepts/limits).
