Skip to main content
Glama
feedmepos

Browser MCP Server

by feedmepos
README.md
# Browser MCP Server

An MCP (Model Context Protocol) server that gives AI agents a real, anti-bot-aware browser via [Zyte API](https://www.zyte.com/zyte-api/) — navigate pages, click/type/scroll, take screenshots, and extract structured data, with automatic proxy/IP rotation and ban avoidance.

Built with [NestJS](https://nestjs.com/) and [`@nestjs-mcp/server`](https://www.npmjs.com/package/@nestjs-mcp/server), talking to Zyte's `/extract` endpoint directly over HTTP. Transport is streamable HTTP only, served stateless at a single `/mcp` endpoint.

## Setup

1. Copy `.env.example` to `.env` and fill in:

   | Variable              | Required | Description                                                                                                |
   | --------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
   | `ZYTE_API_KEY`        | yes      | Your [Zyte API key](https://app.zyte.com/o/zyte-api/api-access)                                            |
   | `ZYTE_API_BASE_URL`   | no       | Override the Zyte API base URL (default `https://api.zyte.com/v1`)                                         |
   | `PORT`                | no       | HTTP port (default `3000`)                                                                                 |
   | `BROWSER_TIMEOUT_MS`  | no       | Per-request timeout for calls to Zyte (default `30000`)                                                    |
   | `BROWSER_MAX_RETRIES` | no       | Retries for rate-limit (429/503) and temporary ban (520) responses, with exponential backoff (default `2`) |

2. Install dependencies and start the server:

   ```bash
   pnpm install
   pnpm run start:dev
   ```

   The MCP endpoint is served at `http://localhost:3000/mcp`.

## Tools

| Tool                 | Returns                                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `browser_browse`     | Opens a URL, optionally runs actions (click/type/select/scroll/wait), returns a `sessionId`, rendered HTML, and optionally a screenshot |
| `browser_screenshot` | Screenshot of a page, optionally after running actions                                                                                  |
| `browser_extract`    | AI-extracted structured JSON matching a JSON Schema you provide                                                                         |
| `browser_search`     | Structured web search results (title/url/snippet) via Zyte's Search API — use to find pages, then open them with `browser_browse`       |

`browser_browse`, `browser_screenshot`, and `browser_extract` accept an optional `sessionId` (returned from a previous call) to reuse the same IP address and cookie jar across multiple tool calls — useful for staying logged in or keeping a consistent IP across a multi-step task. Note that, per [Zyte's session docs](https://docs.zyte.com/zyte-api/usage/features.html#sessions), this does **not** persist actual browser/tab state (in-page form input, scroll position, open modals, etc.) between calls — each call loads a fresh page using the same network identity and cookies.

`actions` is a sequence of steps run before the result is captured: `click`, `type`, `select`, `hover`, `keyPress`, `scrollBottom`, `scrollTo`, `waitForSelector`, `waitForTimeout`, `waitForNavigation`, `goto`, `evaluate`. Element-targeting actions take a `selector: { type: "css" | "xpath", value: string }`.

`browser_search` posts to Zyte's separate `/v1/search` endpoint (not `/v1/extract`) and takes a `domain` (a supported search engine, e.g. `"google.com"`), a `query`, and optional `maxResults` (10-100)/`geolocation`/`locale`.

Rate-limit (429/503) and temporary-ban (520) responses from Zyte are retried automatically with exponential backoff, up to `BROWSER_MAX_RETRIES` times, per [Zyte's error handling guidance](https://docs.zyte.com/zyte-api/usage/errors.html). Other error responses (invalid request, permanent ban, account suspended) surface immediately, with Zyte's error `type` included in the message where available.

## Connecting a client

Example remote-MCP client configuration (e.g. Claude Desktop / Claude Code) pointing at a running instance:

```json
{
  "mcpServers": {
    "browser": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

## Testing

```bash
pnpm test        # unit tests (service + resolver, global fetch mocked)
pnpm run lint    # eslint
pnpm run build   # type-check + compile
```

Unit tests mock the global `fetch` used to call Zyte. To verify end-to-end against the live API, run the server with a real `ZYTE_API_KEY` and use `pnpm dlx @modelcontextprotocol/inspector http://localhost:3000/mcp`, or `curl`, to run `initialize` → `tools/list` → `tools/call`.