Skip to content

Guides · Agents and automation

Proxy MCP server for AI agents

An agent that reads the web from your own address gets your country, your session history and your rate limit. An MCP server closes that gap: the agent asks which countries are online, builds a proxy URL for the one it needs, checks it works and tops up its own balance, all as ordinary tool calls.

Published Updated

Short answers

What does MCP do here?

The Model Context Protocol is an open standard for giving a model tools. It is not a proxy protocol and it does not carry your traffic: the MCP server exposes account and connection operations as tools, and the agent then connects through the gateway with an ordinary HTTP or SOCKS5 proxy URL.

Which tools does the server expose?

get_traffic, list_countries, build_proxy_url, test_connection, buy_traffic, get_pricing and get_status. They are the same routes the dashboard uses, so anything an agent can do through them you can reproduce with curl.

How does the agent pick a country it can actually use?

list_countries reads the live per-country stock, per pool. An agent that calls it first never asks for a country with nothing online, which is the single most common avoidable failure: the gateway answers 502 with E_NO_STOCK_COUNTRY rather than substituting a neighbouring country.

Can an agent buy traffic on its own?

buy_traffic spends the account balance and needs two things before it will do anything: confirm: true and an idempotency key. The confirmation makes a purchase a deliberate step rather than a side effect, and the key means a retried tool call cannot buy twice.

Should the agent rotate its IP during a run?

Usually not. One sticky session for the length of a task keeps cookies, consent state and a shopping flow coherent; rotating mid-task throws away the state the agent just built. Use a new session name for the next task instead.

What does an agent run cost in GB?

It depends almost entirely on whether the agent renders pages. A tool-driven agent fetching HTML and JSON spends tens of kilobytes per step; the same agent driving a headless browser spends megabytes per page. Both are metered the same way, in both directions at the gateway.

Does the API key go to the model?

Give the MCP server the key through its environment, not through a prompt. The server holds it and the model calls tools; nothing about MCP requires a key to appear in the conversation, and nothing should put it there.

What an agent needs that a plain API key does not give it

Agents fail on the web for dull, geographic reasons. The page renders in the wrong language, prices arrive in the wrong currency, a country-restricted page refuses, or fifty parallel runs share one address and get rate limited together.

The fix is a country-accurate exit per run, chosen at the moment of the run. That is a decision the agent has to make while it works, which is exactly what a tool call is for.

The tools, and what each one answers

list_countries
Countries with devices online now, per pool. Call it before choosing, not once at startup.
build_proxy_url
Validates pool, country, rotation, session and protocol, and returns a usable proxy URL.
test_connection
Sends one request through the gateway from our side and reports whether it worked, the exit IP and the latency. It does not confirm the country. Rate limited to six a minute.
get_traffic
Balance and expiry: gb_total, gb_used, gb_left, plus the credentials and the account limits.
buy_traffic
Tops up from the account balance. Needs confirm: true and an idempotency key.
get_pricing
The per-GB ladder, so an agent can reason about a top-up before making one.
get_status
Service status, for an agent deciding whether a failure is worth retrying.

Connecting the server

The server runs over stdio and takes two environment values: the API key and the API base URL. Most MCP clients read a block like this from their configuration file.

Keep the key in the client configuration or a secret store, never in a prompt. There is no sandbox: every key acts on your real balance, whatever its prefix.

MCP client configurationjson
{
  "mcpServers": {
    "portproof": {
      "command": "npx",
      "args": ["-y", "@portproof/mcp"],
      "env": {
        "PORTPROOF_API_KEY": "pk_...",
        "PORTPROOF_API_URL": "https://api.portproof.org/v1"
      }
    }
  }
}

Every tool wraps a route you can call yourself, which is the fastest way to see what the agent will get back before you let it loose.

The same three calls, without an agentsh
# what is online now, per pool: { "mobile": [ { "country": "US", "name": "...", "online": 33 } ], "residential": [ ... ] }
curl https://api.portproof.org/v1/traffic/countries -H "Authorization: Bearer $PORTPROOF_API_KEY"

# build and validate a URL for one run (the API takes mobile / residential / best)
curl -X POST https://api.portproof.org/v1/traffic/build-url \
  -H "Authorization: Bearer $PORTPROOF_API_KEY" -H "Content-Type: application/json" \
  -d '{"pool":"residential","country":"US","rotation":"sticky","session":"run_a1b2c3","protocol":"http"}'

# balance before and after the run
curl https://api.portproof.org/v1/traffic -H "Authorization: Bearer $PORTPROOF_API_KEY"

One session per run

Give each run its own session name and hold it for the whole task: sid-<name> with rot-sticky. The name is lower-case letters, digits and underscores, and a run id or task id makes a good one because it is unique and already in your logs.

Holding a session keeps the device, not the address, so write the run to survive a change rather than asserting on the exit address. Why that distinction matters, and what to do when the address moves mid-task, is in why a sticky session changed its IP.

The stock check is the part worth copying: it turns an unavoidable 502 into a decision the agent makes with its eyes open. Note that the API names the pools mobile and residential, while the proxy username uses mbl and peer.

A run that picks a live country, then holds one sessionpython · requests
import os, uuid
import requests

API = "https://api.portproof.org/v1"
HEAD = {"Authorization": "Bearer " + os.environ["PORTPROOF_API_KEY"], "Content-Type": "application/json"}
WANT = ["US", "NL", "GB"]          # in order of preference


def pick_country(pool: str, need: int = 1) -> str:
    """First preferred country with at least this many devices online in that pool."""
    live = requests.get(API + "/traffic/countries", headers=HEAD, timeout=10).json()
    online = {row["country"]: row["online"] for row in live[pool]}
    for code in WANT:
        if online.get(code, 0) >= need:
            return code
    raise RuntimeError("none of the preferred countries is online in " + pool)


country = pick_country("residential")
session = "run_" + uuid.uuid4().hex[:10]
built = requests.post(
    API + "/traffic/build-url",
    headers=HEAD,
    json={"pool": "residential", "country": country, "rotation": "sticky", "session": session, "protocol": "http"},
    timeout=10,
).json()

print(built["username"], built["url_masked"])
proxies = {"http": built["url"], "https": built["url"]}
print(requests.get("https://api.portproof.org/v1/echo-ip", proxies=proxies, timeout=30).json())
Two names to keep straight: the API calls the pools mobile and residential, and the proxy username calls them mbl and peer. Both are on the setup docs. If the agent runs inside a workflow tool rather than an MCP client, the same job is done with environment variables, as in use a proxy in n8n.

Topping up from inside a run

An agent that stops halfway because the balance ran out is worse than one that asks. buy_traffic exists for that, and it is deliberately awkward: without confirm: true it does nothing, and without an idempotency key the write is refused. A retried tool call with the same key is the same purchase, not a second one.

Keep the decision human where it matters. A sensible pattern is a hard ceiling in your own code, an alert at 80 percent of the balance, and an agent permitted to top up only up to a size you set. Buying from the API spends the account balance; every other payment method goes through checkout.

Budgeting a run, and reading it back

Size the run before it starts: steps times the weight of a step, times the retries you expect. A rendered page is the expensive unit, so an agent that can answer from an API or from HTML should not open a browser to do it.

Afterwards, read gb_used back and store it with the run id. Usage refreshes every five minutes, so compare across a wider window than a short run takes. What the meter counts, including the handshakes and the failed attempts, is in what counts as proxy traffic.

Price per GB by order size, EUR
Order sizePer GB
1 to 4 GBEUR 4.50
5 to 24 GBEUR 3.90−13 %
25 to 49 GBEUR 3.50−22 %
50 to 99 GBEUR 3.20−29 %
100 to 249 GBEUR 2.90−36 %
250 GB and moreEUR 2.50−44 %

GB never expire. What you buy stays on your balance until you use it; a new purchase adds to the same balance. The trial is 0.5 GB for EUR 2.90, once per customer.

Agent runs are metered like any other traffic: the same ladder, the same balance, no fee per session or per connection.

For the wider picture of what agents do with this, see proxies for AI agents, the two pools at mobile 4G/5G and residential, and the per-GB ladder on pricing.

What is not allowed

What is not allowed: an agent is held to the same rules as a person. Creating or farming accounts, solving or evading human checks, credential stuffing and anything else on the declined list are refused, whoever or whatever issues the request. Give an agent a task you would be happy to run yourself. See the acceptable-use policy.

Proxy MCP server for AI agents · Portproof