FastMCP service managing a pool of SOCKS5 proxies for dynamic agent spawning. When a spawner creates a new agent container, it calls get_proxy() to lease a proxy, injects SOCKS_PROXY as an env var, and calls release_proxy() when the task finishes.
Agents route all traffic through the proxy transparently via tun2socks — no application-level proxy config needed.
# Install
cd apps/proxy_mcp
uv sync
# Start with Docker (Postgres + app)
docker compose up -d
# Or run locally (requires Postgres)
export PROXY_MCP_DATABASE_URL=postgresql+psycopg://proxy_mcp:proxy_mcp@localhost:5432/proxy_mcp
alembic upgrade head
proxy-mcp serve --transport streamable-http
| Tool | Description |
|---|---|
get_proxy | Lease a proxy. Inline rotating products bypass the DB; static IPs use SELECT FOR UPDATE SKIP LOCKED. Cheapest-first by priority. |
release_proxy | Release a lease after successful task completion. No-op for inline rotating leases. |
report_failure | Record a failure. Static IPs auto-disable after 3 consecutive fails (configurable). Inline rotating leases log + return without state change. |
list_proxies | Read-only pool overview. Returns DB rows unioned with synthesized rows for configured rotating products. |
upsert_proxy | Insert or update a proxy. Upserts on (host, port, protocol). Used by cli sync for static IPs. |
proxy-mcp serve [--transport stdio|streamable-http] [--host HOST] [--port PORT]
proxy-mcp sync [--provider brightdata|oxylabs|decodo|dataimpulse]
# fetch static IPs from provider APIs
# rotating products are skipped (zero-row mode)
proxy-mcp list [--enabled-only] [--country US]
proxy-mcp reset-leases # force-release all static leases
proxy-mcp enable PROXY_ID
proxy-mcp disable PROXY_ID
Lower priority = cheaper = selected first when no proxy_type filter is passed.
| Type | Priority | Typical Cost (Oxylabs) |
|---|---|---|
free | 10 | $0 |
datacenter | 20 | $1.2 / IP |
isp | 25 | $1.6 / IP |
dedicated_datacenter | 28 | $2.25 / IP |
dedicated_isp | 32 | $3.2 / IP |
residential | 40 | $6 / GB |
mobile | 50 | $7.5 / GB |
Rotating products (BrightData zones, Oxylabs residential/mobile, Decodo rotating, DataImpulse) are leased inline from settings at request time. Each lease constructs a fresh URL with a session ID injected into the username, pointing at the provider's gateway. Nothing is written to the database.
cli sync required.inline:<provider>:<product>:<country>:<session> so release_proxy and report_failure can short-circuit without DB writes.app/inline_lease.py for the candidate iterator + URL builder.cli sync requiredStatic IP products (BrightData static zones, Oxylabs ISP / Datacenter / dedicated-*, Decodo static) carry per-IP state. cli sync fetches the current list from each provider's API and upserts rows into the proxies table. get_proxy then locks one with SELECT FOR UPDATE SKIP LOCKED and marks it in_use=True.
Run proxy-mcp sync once after seeding credentials, then on a cron (daily is plenty — provider IP lists rarely churn).
BRIGHTDATA_API_TOKEN=<bearer>
BRIGHTDATA_CUSTOMER_ID=<your customer id>
BRIGHTDATA_ZONES='[
{"name":"res_zone","password":"<pw>","type":"rotating","proxy_type":"residential"},
{"name":"dc_static","password":"<pw>","type":"static","proxy_type":"datacenter"}
]'
brd.superproxy.io:22225 for rotating-session-{sid} appended to the usernameGET /api/zone/ips?zone={name} (Bearer auth)OXYLABS_USERNAME=<sub-account>
OXYLABS_PASSWORD=<sub-account password>
OXYLABS_PRODUCTS='[
{"name":"residential","type":"rotating","proxy_type":"residential"},
{"name":"mobile","type":"rotating","proxy_type":"mobile"},
{"name":"isp_shared","type":"static","proxy_type":"isp",
"list_url":"https://stats.oxylabs.io/api/proxy-list/isp"},
{"name":"isp_dedicated","type":"static","proxy_type":"dedicated_isp",
"list_url":"https://stats.oxylabs.io/api/proxy-list/dedicated-isp"},
{"name":"dc_dedicated","type":"static","proxy_type":"dedicated_datacenter",
"list_url":"https://stats.oxylabs.io/api/proxy-list/dedicated-datacenter"}
]'
pr.oxylabs.io:7777 for rotating-sessid-{sid} appended to the usernamelist_url from your dashboard (HTTP Basic auth)DECODO_API_TOKEN=<token>
DECODO_USERNAME=<sub-account>
DECODO_PASSWORD=<sub-account password>
DECODO_PRODUCTS='[
{"name":"rot_residential","type":"rotating","proxy_type":"residential"}
]'
gate.smartproxy.com:5000 (SOCKS5) / :7000 (HTTP) for rotating-sessionid-{sid} appended to the usernameapp/providers/decodo.py:13-15DATAIMPULSE_LOGIN=<login>
DATAIMPULSE_PASSWORD=<password>
DATAIMPULSE_PRODUCTS='[
{"name":"rot_residential","country":"GB","proxy_type":"residential"}
]'
gw.dataimpulse.com:824 (SOCKS5)__cr.<lower> segment in the username;sessid.{sid} appended (semicolon + dot delimiters preserved literally in URL encoding)# Required
PROXY_MCP_DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/proxy_mcp
# Optional
PROXY_MCP_HOST=0.0.0.0
PROXY_MCP_PORT=8002
PROXY_MCP_AUTO_DISABLE_FAILS=3 # consecutive fails before static IP auto-disable
PROXY_MCP_LEASE_TIMEOUT=60 # minutes before stale lease cleanup (static IPs only)
LOG_LEVEL=INFO
API_KEY= # bearer auth for streamable-http mode (Core sets this on its calls)
Set credentials + the corresponding *_PRODUCTS (or *_ZONES for BrightData) JSON array. The full reference is in .env.example. Each entry must include type (rotating or static) and proxy_type (one of the seven values listed above). Country is optional.
PROXY_MCP_DATABASE_URL=postgresql+psycopg://proxy_mcp:proxy_mcp@db:5432/proxy_mcp
API_KEY=<bearer Core sends>
OXYLABS_USERNAME=<account>
OXYLABS_PASSWORD=<password>
OXYLABS_PRODUCTS='[{"name":"residential","type":"rotating","proxy_type":"residential"}]'
Start the service. Set PROXY_MCP_URL=http://proxy-mcp:8002/mcp and PROXY_AGENTS=linux.chrome.agent on Core. No cli sync needed — every Chrome agent spawn now leases an inline residential proxy.
cli sync| Setup | Sync needed? |
|---|---|
| Pure rotating (Oxylabs residential, DataImpulse, BrightData rotating zones, Decodo rotating) | No |
| Static IPs (Oxylabs ISP / DC / dedicated-*, BrightData static zones, Decodo static) | Yes — daily cron is plenty |
| Mixed (rotating + static from the same provider) | Yes for the static portion; rotating items in the same *_PRODUCTS JSON are skipped automatically with a 'N rotating skipped' note |
Core's spawner injects the lease as an env var on the agent pod:
# Browser / desktop / scraper / terminal / downloader agents (tun2socks routing)
SOCKS_PROXY=socks5://user:[email protected]:1080
# Android agent (ADB http_proxy)
ANDROID_PROXY=1.2.3.4:1080
Both values come back in the ProxyLease response. Core picks the right env name based on agent_type_key.
See specs/PLAN_PROXY_INTEGRATION.md (Core-side wiring) and specs/PLAN_PROXY_ZERO_ROW.md (this service's lease model) for the design behind both halves.
uv sync --dev
uv run pytest tests/ -v
Content type
Image
Digest
sha256:f0e87bbab…
Size
83.6 MB
Last updated
4 months ago
docker pull superbizon007/proxy-mcp