# MCP Server

Source: https://docs.evomi.com/integrations/mcp/

The **Evomi MCP Server** exposes Evomi's Public API and Scraper API over the Model Context Protocol. AI assistants like Claude can scrape and crawl websites, and manage the proxies on your account — credentials, geo targeting, sticky sessions, usage and balances — from the same conversation.

39 tools by default, 40 with `EVOMI_ENABLE_SPENDING=1`.

---

## Installation

```bash
pip install evomi-mcp
```

Needs Python 3.10 or newer. The server runs on both major versions of the `mcp` SDK (`>=1.8.0,<3`), so it installs into an environment already pinned to `mcp` 1.x as well as a fresh one that resolves 2.x.

`evomi-mcp --version` prints the installed version and `evomi-mcp --help` lists the environment variables it reads. With no arguments it speaks MCP over stdio, which is how an MCP client starts it.

---

## Configuration

One credential is enough:

```bash
export EVOMI_PUBLIC_API_KEY="your-public-api-key"
```

Take it from [Settings > API](https://my.evomi.com/settings/api) for a personal account, or Settings > Team for a team one. It authenticates the proxy and account tools directly. The scraping tools authenticate with the account's scraper key, which the server reads from the Public API on the first scraping call and holds in memory for the process. That key is never written to disk and never appears in tool output.

| Variable | Key | Used by |
| :--- | :--- | :--- |
| `EVOMI_PUBLIC_API_KEY` | Public API key, from [Settings > API](https://my.evomi.com/settings/api) | Proxy credentials, usage, targeting, sessions, and indirectly everything else |
| `EVOMI_SCRAPER_API_KEY` | Scraper API key, from the same page | Optional. Pins a specific key for scraping, crawling, configs, schemas, storage and schedules, and skips the lookup |
| `EVOMI_API_KEY` | Fallback for either of the above | Both, wherever the specific variable is unset |

Where a specific variable is set, it wins over `EVOMI_API_KEY`.

The API endpoints can be pointed elsewhere, and default to:

```bash
export EVOMI_PUBLIC_BASE_URL="https://api.evomi.com"
export EVOMI_BASE_URL="https://scrape.evomi.com"
```

### Safety Settings

Both of these change which tools exist or what they return, so decide on them when you set the server up rather than later:

| Variable | Effect |
| :--- | :--- |
| `EVOMI_HIDE_PROXY_PASSWORDS=1` | Masks every proxy password and service API key. The mask is a fixed string that carries none of the secret's characters |
| `EVOMI_ENABLE_SPENDING=1` | Registers `order_browser_profile`, the one tool that spends account balance. Without it the tool is not registered at all, so the connected model never sees it |

With `EVOMI_HIDE_PROXY_PASSWORDS=1` set:

| Tool | Behaviour |
| :--- | :--- |
| `get_proxy_credentials` | **Masked.** Username, gateway, ports and balance are unchanged |
| `get_api_access` | **Masked**, even when `include_api_key: true` is passed explicitly |
| `build_proxy_connection_string` | **Refuses**, naming the variable |
| `generate_proxy_list` | **Refuses** before the API is called, so the bulk credentials are never minted |
| Every other tool | Unchanged. None of them returns a credential |

The two that refuse do so because their entire output is the credential. The refusal points at `list_proxy_products` and `list_proxy_targeting_options`, which give the gateway hostname, ports and targeting values with no credential in them.

### Credentials in Tool Output

`get_proxy_credentials`, `build_proxy_connection_string` and `generate_proxy_list` return live proxy passwords, and `get_api_access` returns a service API key when asked with `include_api_key`. Those values become part of the conversation with whatever model is connected, and part of whatever that client logs. Every other tool returns balances, endpoints, usage and targeting data only.

Three limits apply either way. The four tools above carry an instruction in their descriptions not to repeat the value back unless it was asked for directly. `build_proxy_connection_string` and `generate_proxy_list` return at most 50 entries per call, and refuse a larger request with the limit rather than clamping it. And the `curl_example` that comes with a connection string has its password masked, since it is the field most likely to be pasted into a terminal, a ticket or a screenshot — pass `runnable_curl_example: true` for the form that can be run. The connection strings themselves carry the real password either way.

API keys are read from the environment and sent as headers. They are scrubbed from tool output and error messages.

---

## Usage with Claude Desktop

Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "evomi": {
      "command": "evomi-mcp",
      "env": {
        "EVOMI_PUBLIC_API_KEY": "your-public-api-key"
      }
    }
  }
}
```

Or if installed from source:

```json
{
  "mcpServers": {
    "evomi": {
      "command": "python",
      "args": ["-m", "evomi_mcp.server"],
      "env": {
        "EVOMI_PUBLIC_API_KEY": "your-public-api-key"
      }
    }
  }
}
```

---

## Available Tools (39 Total)

Thirty-nine tools are registered by default. `order_browser_profile` brings that to forty, and only when `EVOMI_ENABLE_SPENDING=1` is set.

### Proxy & Account (9 tools, Evomi Public API, + 1 opt-in)

| Tool | Description | Returns credentials |
| :--- | :--- | :--- |
| `list_proxy_products` | Products on the account with endpoints, ports, balance and username | No |
| `get_proxy_credentials` | Proxy username and password for one product | **Yes** |
| `build_proxy_connection_string` | Connection strings with geo targeting, sessions and expert filters, plus a curl check | **Yes** |
| `generate_proxy_list` | Bulk proxy list (up to 50 per call) from the Public API generator | **Yes** |
| `get_proxy_usage` | Bandwidth used over 24h / 3d / 7d, total and per bucket | No |
| `list_proxy_targeting_options` | Searchable countries, regions, cities, ISPs and continents per product, each with the `id` the gateway accepts and its display name | No |
| `rotate_proxy_session` | **Mutating** — force a sticky session onto a new exit IP | No |
| `get_api_access` | Scraper and Browser access, credits, concurrency and endpoints (keys masked by default) | Only on request |
| `list_browser_profiles` | Saved browser fingerprint profiles | No |
| `order_browser_profile` | **Mutating, spends money** — orders a browser fingerprint profile, charged against the account's data balance. Only registered when `EVOMI_ENABLE_SPENDING=1` | No |

`get_proxy_usage` covers the four products billed by bandwidth: Premium Residential, Core Residential, Mobile and Datacenter. Static Residential is billed per rented IP instead, so `list_proxy_products` is where its IPs and their expiry dates are.

### Scraping Operations (6 tools)

| Tool | Description |
| :--- | :--- |
| `scrape_url` | Scrape a single URL with configurable options |
| `crawl_website` | Crawl a website to discover and scrape multiple pages |
| `map_website` | Discover URLs from a website |
| `search_domains` | Find domains by searching the web |
| `agent_request` | AI-powered conversational scraping assistant |
| `get_task_status` | Check the status of an async task |

### Config Management (6 tools)

| Tool | Description |
| :--- | :--- |
| `list_configs` | List all saved scrape configurations |
| `create_config` | Create a new scrape configuration |
| `get_config` | Get a saved scrape configuration by ID |
| `update_config` | Update an existing scrape configuration |
| `delete_config` | Delete a scrape configuration |
| `generate_config` | Generate a scrape config from natural language using AI |

### Schema Management (6 tools)

| Tool | Description |
| :--- | :--- |
| `list_schemas` | List all saved extraction schemas |
| `create_schema` | Create a new extraction schema |
| `get_schema` | Get a saved extraction schema by ID |
| `update_schema` | Update an existing extraction schema |
| `delete_schema` | Delete an extraction schema |
| `get_schema_status` | Get the test status of an extraction schema |

### Storage Management (4 tools)

| Tool | Description |
| :--- | :--- |
| `list_storage_configs` | List all storage configurations |
| `create_storage_config` | Create a new storage configuration |
| `update_storage_config` | Update an existing storage configuration |
| `delete_storage_config` | Delete a storage configuration |

### Schedule Management (7 tools)

| Tool | Description |
| :--- | :--- |
| `list_schedules` | List all scheduled scrape jobs |
| `create_schedule` | Create a new scheduled scrape job |
| `get_schedule` | Get a scheduled job by ID |
| `update_schedule` | Update an existing scheduled job |
| `delete_schedule` | Delete a scheduled job |
| `toggle_schedule` | Toggle a scheduled job active/inactive |
| `list_schedule_runs` | Get execution history for a scheduled job |

### Account (1 tool)

| Tool | Description |
| :--- | :--- |
| `get_account_info` | Get account information including credit balance |

### Tool Annotations

Every tool sets all four hints from the MCP spec — `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint` — so a client that gates on them can tell `list_configs` apart from `delete_config`. `openWorldHint` is true for the tools that reach an address the caller chose, and false for those that only talk to Evomi's own endpoints. `readOnlyHint` is false for anything that creates, updates, deletes, toggles, rotates or orders. The `MUTATING` prefix on `rotate_proxy_session` and `order_browser_profile` is in the tool description as well, since a description reaches every model where an annotation only reaches a client that reads it.

A failed call comes back as a normal result with `isError=True` and the reason in the body, not as a transport-level error. Where the Scraper API rejected the request, that body carries the API's own explanation of the parameter it objected to along with the status, so the next attempt can correct it.

---

## Tool Examples

### Proxy Connection Strings

```json
// A US residential proxy
{"product": "rp", "countries": ["US"]}

// Berlin, sticky session held for 30 minutes, three strings
{"product": "rp", "countries": ["DE"], "city": "Berlin", "session": "sticky", "lifetime_minutes": 30, "count": 3}

// Datacenter over SOCKS5, in host:port:user:pass format
{"product": "dcp", "countries": ["GB"], "protocol": "socks5", "format": "2"}

// With a curl example that can be pasted and run as it stands
{"product": "rp", "countries": ["US"], "runnable_curl_example": true}
```

Call `list_proxy_targeting_options` first whenever a city, region or ISP is involved. Every entry it returns carries an `id`, which is what the gateway accepts, and a `name`, which is the display form. Cities, regions and continents take either — `North America` and `north.america` reach the same pool. Countries are two-letter ISO 3166-1 alpha-2 codes, so the United Kingdom is `GB`. An ISP is the one value that has to be the `id`, since the catalogue abbreviates its label.

A value the gateway does not take is refused with a pointer to what it does, rather than encoded into a string that fails at connect time. City and region targeting take at most one country, and work without one.

### Scraping

```json
// Basic scrape
{"url": "https://example.com"}

// AI extraction
{"url": "https://example.com/products", "ai_enhance": true, "ai_prompt": "Extract product names and prices"}

// Browser mode with actions
{"url": "https://example.com", "mode": "browser", "js_instructions": [{"click": ".accept-cookies"}, {"wait": 1000}]}
```

### Crawling

```json
// Basic crawl
{"domain": "example.com", "max_urls": 50}

// With URL filter
{"domain": "example.com", "url_pattern": "/products/", "depth": 3}
```

### Domain Search

```json
// Find domains
{"query": "best e-commerce sites in Germany", "max_urls": 20, "region": "de-de"}

// Up to 10 queries in one call, max_urls applying to each
{"query": ["online bookstores", "book shops UK"], "max_urls": 50}
```

### Config Management

```json
// Create config
{"name": "Product Scraper", "config": {"mode": "browser", "output": "markdown"}}

// Generate config with AI
{"name": "Amazon Scraper", "prompt": "Scrape product title, price, and reviews from Amazon"}
```

### Scheduling

```json
// Create daily schedule
{"name": "Daily Prices", "config_id": "cfg_abc123", "interval_minutes": 1440, "start_time": "09:00"}
```

---

## Running the Server Directly

```bash
evomi-mcp
# or
python -m evomi_mcp.server
```

---

## Credits

Scraping, crawling, schema and schedule operations consume Scraper API credits, and what a call costs depends on the mode it runs in and the options it carries. Current rates are in [Pricing at a Glance](/scraping-products-instructions/scraper-api/#pricing-at-a-glance).

Each scraping response carries `credits_used` and `credits_remaining`, so the cost of a call is visible in its own result. `get_api_access` reports the balance on the account.

The proxy tools go through the Public API and spend no Scraper API credits. Reading credentials, usage or targeting options is free, and the traffic you then send through a proxy draws on the product's data balance, which `list_proxy_products` reports. `order_browser_profile` charges that balance directly, with no way to undo the charge, which is why it exists only under `EVOMI_ENABLE_SPENDING=1`.

---

## Resources

| Resource | Link |
| :--- | :--- |
| GitHub Repository | [github.com/evomi/evomi-mcp](https://github.com/evomi/evomi-mcp) |
| PyPI Package | [pypi.org/project/evomi-mcp](https://pypi.org/project/evomi-mcp/) |
| Scraper API | [Scraper API docs](/scraping-products-instructions/scraper-api/) |
| Public API | [Public API docs](/public-api/) |
| MCP Specification | [modelcontextprotocol.io](https://modelcontextprotocol.io/) |
