> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfsky.io/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

> Give Claude Code, Cursor, Codex, Grok or another MCP client a Surfsky cloud browser as tools.

The Surfsky MCP server lets an agent read pages through the [Scraping API](https://docs.surfsky.io/quickstart/scraping_api.md) and control a cloud browser: navigate, click, fill forms, take screenshots. Surfsky hosts it at `https://mcp.surfsky.io/mcp`, so nothing is installed on your machine.

To build Surfsky into your own project instead, give your agent the [setup prompt](https://docs.surfsky.io/coding-agents.md).

## Connect to the hosted server

Add the server by its URL, with no token. The app opens a Surfsky page in your browser: sign in or sign up, check which app is asking, and click Allow. The app receives its own expiring token and never sees your API token.

> [tab] Claude Code
>
> ```bash theme={null}
> claude mcp add --transport http surfsky https://mcp.surfsky.io/mcp
> claude mcp login surfsky
> ```
>
> The server is added for the current project folder. Add `--scope user` to the first command to have it in every project.

> [tab] Cursor
>
> [![Add Surfsky to Cursor](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=surfsky\&config=eyJ1cmwiOiJodHRwczovL21jcC5zdXJmc2t5LmlvL21jcCJ9)
>
> The button opens Cursor with the server filled in. Or put this in `~/.cursor/mcp.json` (`.cursor/mcp.json` for one project):
>
> ```json theme={null}
> {
>   "mcpServers": {
>     "surfsky": {
>       "url": "https://mcp.surfsky.io/mcp"
>     }
>   }
> }
> ```
>
> Cursor then asks you to sign in to the server.

> [tab] Codex
>
> ```bash theme={null}
> codex mcp add surfsky --url https://mcp.surfsky.io/mcp
> codex mcp login surfsky
> ```

> [tab] Grok Build
>
> ```bash theme={null}
> grok mcp add --transport http surfsky https://mcp.surfsky.io/mcp
> ```
>
> Grok opens the sign-in when it first connects to the server.

### Claude, ChatGPT and Grok apps

The chat apps add the server as a custom connector with the URL `https://mcp.surfsky.io/mcp`, then send you through the same sign-in.

> [tab] Claude
>
> 1. Open [Customize > Connectors](https://claude.ai/customize/connectors) and click **Add**, then **Add custom connector**.
> 2. Enter a name and the URL. Keep the settings Claude detects: **Sign in now** and **Register automatically**.
> 3. Click **Add**, sign in and click Allow.
>
> The connector works on claude.ai and in Claude Desktop under the same account. In a chat, turn it on under **+** > **Connectors**. In a Team or Enterprise organization an owner adds it under Organization settings > Connectors, and each member clicks **Connect**.

> [tab] ChatGPT
>
> 1. On [chatgpt.com/plugins](https://chatgpt.com/plugins) click the plus button, then **Add custom MCP server**.
> 2. Enter a name and the URL, and choose **OAuth** as authentication.
> 3. Accept the warning, click **Create as a plugin**, then install the plugin and sign in.
>
> In a chat, type `@` and pick the plugin. Your workspace's policies decide whether custom servers are allowed.

> [tab] Grok
>
> 1. Open [grok.com/connectors](https://grok.com/connectors) and click **New Connector**, then **Custom**.
> 2. Enter the URL, sign in and click Allow.

### With an API token

Scripts, CI and clients without the login send your [API token](https://docs.surfsky.io/quickstart.md#before-you-start) as a bearer header. The server finds your account's API host itself. Replace `YOUR_API_TOKEN`:

> [tab] Grok Build
>
> ```bash theme={null}
> grok mcp add --transport http surfsky https://mcp.surfsky.io/mcp --header "Authorization: Bearer YOUR_API_TOKEN"
> ```

> [tab] Claude Code
>
> ```bash theme={null}
> claude mcp add --transport http surfsky https://mcp.surfsky.io/mcp --header "Authorization: Bearer YOUR_API_TOKEN"
> ```

> [tab] Cursor
>
> ```json theme={null}
> {
>   "mcpServers": {
>     "surfsky": {
>       "url": "https://mcp.surfsky.io/mcp",
>       "headers": { "Authorization": "Bearer YOUR_API_TOKEN" }
>     }
>   }
> }
> ```

> [tab] Codex
>
> Codex reads the token from an environment variable each time it starts, so set the variable in your shell profile:
>
> ```bash theme={null}
> export SURFSKY_API_TOKEN='YOUR_API_TOKEN'
> codex mcp add surfsky --url https://mcp.surfsky.io/mcp --bearer-token-env-var SURFSKY_API_TOKEN
> ```

> [tab] Other clients
>
> Any client that speaks streamable HTTP and can send a header:
>
> ```text theme={null}
> URL:    https://mcp.surfsky.io/mcp
> Header: Authorization: Bearer YOUR_API_TOKEN
> ```

A missing or wrong token is refused with HTTP 401 when the client connects.

> [!NOTE]
>
> Accounts on a development cluster are not served by the hosted server.

## Connected apps

Every app you allowed is listed in the **Connected apps** card of the [dashboard](https://app.surfsky.io), under the API key, with the date you allowed it and when it was last used. The same app allowed on two machines is two entries.

* **Revoke** cuts one app off. It is refused within an hour, and a browser it left running stops after five minutes without a tool call. The app can connect again by going through the sign-in.
* Regenerating the API key does not disconnect apps.
* An app that goes unused for 90 days expires and has to sign in again.

## Run it locally

The local server runs on your machine with `uvx` and needs [uv](https://docs.astral.sh/uv/getting-started/installation/); `uv` downloads Python itself. Replace `...` with your [API token and base URL](https://docs.surfsky.io/quickstart.md#before-you-start).

> [tab] Claude Code
>
> ```bash theme={null}
> claude mcp add surfsky -e SURFSKY_API_TOKEN=... -e SURFSKY_API_BASE_URL=... -- uvx surfsky-mcp
> ```

> [tab] Claude Desktop, Cursor
>
> ```json theme={null}
> {
>   "mcpServers": {
>     "surfsky": {
>       "command": "uvx",
>       "args": ["surfsky-mcp"],
>       "env": { "SURFSKY_API_TOKEN": "...", "SURFSKY_API_BASE_URL": "..." }
>     }
>   }
> }
> ```
>
> Claude Desktop on macOS does not read your shell profile: put the absolute path that `which uvx` prints in `command`. Quit the app completely after changing the config; closing the window leaves the old server running.

> [tab] Codex
>
> In `~/.codex/config.toml`:
>
> ```toml theme={null}
> [mcp_servers.surfsky]
> command = "uvx"
> args = ["surfsky-mcp"]
> env = { SURFSKY_API_TOKEN = "...", SURFSKY_API_BASE_URL = "..." }
> ```

## Tools

| Tool | What it does |
| - | - |
| `surfsky_scrape` | Fetch a URL as Markdown, HTML, links or a screenshot. No session. |
| `surfsky_session_start` | Start a session, with optional profile, proxy and fingerprint settings. |
| `surfsky_session_stop` | Stop a session and its billing. Safe to repeat. |
| `surfsky_session_list` | List the account's running sessions. |
| `surfsky_navigate` | Open a URL, go back or forward, or reload. |
| `surfsky_snapshot` | List page elements with `@N` references for actions. |
| `surfsky_act` | Click, double-click, hover, fill, type, press keys, select or scroll, with [human-like input](https://docs.surfsky.io/human_emulation.md). `steps` runs up to 20 actions in one call. |
| `surfsky_wait` | Wait for an element, URL, text or a delay. |
| `surfsky_read` | Read the page as Markdown, text, HTML or links. |
| `surfsky_screenshot` | Capture the viewport, the full page or an element. The agent takes one only when you ask for an image or the answer depends on how the page looks. |
| `surfsky_evaluate` | Run JavaScript in an isolated context or in the page. |
| `surfsky_tab` | List, open, switch or close tabs. |
| `surfsky_status` | Check credentials, plan limits and proxy quota. |

### Steps

`surfsky_act` with `steps` runs a known sequence, such as filling a form and submitting it, in one call.

* It stops at the first step that fails. The error names the step and returns the page state and a snapshot.
* A call runs for up to 45 seconds, then stops and names the step it reached.
* `@N` references come from the snapshot before the call. After a step that changes the page, target later steps by CSS selector or `text=words`. A reference whose element changed fails with `stale_ref`.

### Bot challenges

`surfsky_navigate` makes one solve of a bot challenge page's own widget before it returns. The result's `note` says what happened:

| `note` | Meaning |
| - | - |
| `challenge_page met, solved` | The widget was solved. On an account that pays for captcha solving the solve is billed like any other, and the note carries the price it reported. |
| `challenge_page met, passed` | The page let the browser through by itself. |
| `challenge_page met, not solved` | The page is still the challenge. `surfsky_scrape` may get it. |

Navigation never solves a captcha on an ordinary page.

### Result size

| Result | Default | To get more |
| - | - | - |
| Text from `surfsky_scrape` and `surfsky_read` | Cut at 10,000 characters, with a marker | `max_chars`, up to 200,000 |
| Snapshot returned by `surfsky_navigate` and `surfsky_act` | First 80 elements and a count of the rest | `surfsky_snapshot` |
| `surfsky_snapshot` | 500 elements | `limit`, or `find` to filter |

`find` matches part of an element's role, name, link or selector; `a|b` matches either term. `surfsky_read` with `selector` and `limit` returns the first matching elements, up to 100, as separate items.

### Snapshots

A snapshot is text, one row per element: `[@12] button "Search"`, with value, checked, options and disabled where present. A link's `href` is listed only when another link has the same name. `surfsky_snapshot` with `details: true` adds every `href` and each row's CSS selector.

## Sessions

A session is one running browser. It is [billed](https://docs.surfsky.io/sessions.md#billing) like any other browser, idle time included, and counts against your plan's [parallel browsers](https://docs.surfsky.io/limits.md#parallel-browsers). `surfsky_scrape` goes through the Scraping API and starts no session.

On the hosted server:

* A session starts only when the agent calls `surfsky_session_start`. Browser tools called before that fail with `no_session`.
* A browser tool called without `session_id` uses the session this connection last started. A client that keeps no connection uses the session its connected app started, or with an API token the only one running. With several sessions running, pass `session_id` on every browser call.
* A session ends on `surfsky_session_stop`, or after five minutes without a tool call, whether or not the client is still connected. `idle_timeout` on `surfsky_session_start` changes the five minutes, up to an hour.

The local server instead starts a default session on the first browser tool call, and stops it after five minutes idle or when the server shuts down.

`surfsky_session_start` returns a `live_url`, a live view of the browser. When a page needs you (a login, a 2FA code, a captcha), the agent gives you this URL and waits instead of asking for credentials in chat. Anyone with the URL controls the browser.

To keep a login between runs, pass a [persistent profile](https://docs.surfsky.io/sessions.md#persistent-profiles) UUID to `surfsky_session_start` as `profile_uuid`.

## Errors

| Error | Meaning |
| - | - |
| `auth` | The token or base URL did not reach the server, or the app was revoked or expired: sign in again. For the local server, check the `env` block of the client's config, not your shell. |
| `no_session` | Hosted server: no session on this connection. Call `surfsky_session_start`. |
| `session_gone` | The session has stopped. Start another; page state is lost. |
| `stale_ref` | The page changed under the `@N` references. Take a new `surfsky_snapshot`. |
| `plan_limit` | Your plan does not include this. |
| `insufficient_balance` | The balance is empty. Top up in the dashboard. |
| `plan_full`, `quota` | Parallel browsers or traffic are used up. `surfsky_session_list` shows what is running; retries do not help. |

Page content reaches the model inside an `<untrusted-page-content>` block marked as data, not instructions. That does not stop every prompt injection: check what an action does before it submits a form or spends money.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.