From 4095cf06feea7c6c25526001e922cc67741b2084 Mon Sep 17 00:00:00 2001 From: drjones Date: Sat, 19 Sep 2026 21:43:45 -0700 Subject: [PATCH] MCP server (streamable-http) wrapping proxy store: 5 tools, nginx /mcp route, Hermes registration, llms.txt/meta discovery --- app.py | 4 ++ clean-proxys-mcp.service | 14 ++++++ mcp_server.py | 97 ++++++++++++++++++++++++++++++++++++++++ nginx.conf | 13 ++++++ templates/base.html | 2 +- 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 clean-proxys-mcp.service create mode 100644 mcp_server.py diff --git a/app.py b/app.py index 94a3c86..4faac4b 100644 --- a/app.py +++ b/app.py @@ -536,6 +536,10 @@ a working proxy — no KYC, Bitcoin billing, programmatic end-to-end. - Billing: pay-per-GB, Bitcoin (BTCPay) - Signup: username + password only (no email, no KYC) +## MCP (for agent clients) +- Streamable HTTP MCP server: {SITE_HOST}/mcp +- Tools: get_info, list_plans, buy_proxy, get_order_status, list_dedicated_locations + ## API - OpenAPI spec: {SITE_HOST}/openapi.json - GET /api/v1/info -> service info (JSON) diff --git a/clean-proxys-mcp.service b/clean-proxys-mcp.service new file mode 100644 index 0000000..c6eac55 --- /dev/null +++ b/clean-proxys-mcp.service @@ -0,0 +1,14 @@ +[Unit] +Description=Clean Proxys MCP server (streamable-http) +After=network.target app.service + +[Service] +WorkingDirectory=/opt/pleiades +ExecStart=/opt/pleiades/mcp-venv/bin/python /opt/pleiades/mcp_server.py +Environment=CLEAN_PROXYS_API=http://127.0.0.1:5000 +Environment=MCP_PORT=8090 +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target diff --git a/mcp_server.py b/mcp_server.py new file mode 100644 index 0000000..ea719f2 --- /dev/null +++ b/mcp_server.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Clean Proxys MCP server — exposes the proxy store as MCP tools for agents. + +Wraps the Clean Proxys REST API (same host). Run as streamable-http on :8090/mcp +so any MCP client (Hermes, Claude, GPTs) can discover and buy proxies natively. +""" +import json +import os + +import httpx +from mcp.server import FastMCP + +API = os.environ.get("CLEAN_PROXYS_API", "http://127.0.0.1:5000") + +mcp = FastMCP( + "clean-proxys", + host="0.0.0.0", # disable DNS-rebinding guard so LAN/remote agents can connect + instructions=( + "Clean Proxys — buy clean residential SOCKS5/HTTP proxies programmatically. " + "No KYC, Bitcoin billing, global geo-targeting. " + "Flow: list_plans -> buy_proxy -> poll get_order_status until paid -> " + "connect using the returned socks5/http strings." + ), +) + + +def _get(path, params=None): + r = httpx.get(f"{API}{path}", params=params, timeout=20) + r.raise_for_status() + return r.json() + + +def _post(path, body): + r = httpx.post(f"{API}{path}", json=body, timeout=25) + r.raise_for_status() + return r.json() + + +@mcp.tool() +def get_info() -> str: + """Service info, value proposition, proxy ports, and billing model.""" + return json.dumps(_get("/api/v1/info"), indent=2) + + +@mcp.tool() +def list_plans() -> str: + """List residential GB plans (id, name, gb, price_sats).""" + return json.dumps(_get("/api/v1/plans"), indent=2) + + +@mcp.tool() +def buy_proxy( + plan_id: int, + country: str = "", + city: str = "", + username: str = "", + password: str = "", +) -> str: + """One-call purchase: creates an account + Bitcoin invoice and returns working + proxy credentials plus a checkout URL. + + Args: + plan_id: plan id from list_plans. + country: ISO country code (US, GB, JP, ...) for geo-targeting; empty = any. + city: optional city. + username: optional account username (auto-generated if empty). + password: optional account password (auto-generated if empty). + + Returns socks5/http connection strings, the proxy password, and an invoice + checkout_url plus status_url to poll for payment. + """ + body = { + "plan_id": plan_id, + "country": country, + "city": city, + "username": username, + "password": password, + } + return json.dumps(_post("/api/v1/proxy", body), indent=2) + + +@mcp.tool() +def get_order_status(order_id: str) -> str: + """Poll the payment status of a proxy order (pending or paid).""" + return json.dumps(_get(f"/api/v1/order/{order_id}"), indent=2) + + +@mcp.tool() +def list_dedicated_locations(product: int = 9) -> str: + """List dedicated IP locations. product: 9=ISP, 3=datacenter.""" + return json.dumps(_get("/dedicated/locations", params={"product": product}), indent=2) + + +if __name__ == "__main__": + mcp.settings.host = "0.0.0.0" + mcp.settings.port = int(os.environ.get("MCP_PORT", "8090")) + mcp.run(transport="streamable-http", mount_path="/mcp") diff --git a/nginx.conf b/nginx.conf index a4a6ccc..59bdfe6 100644 --- a/nginx.conf +++ b/nginx.conf @@ -9,6 +9,19 @@ server { expires 7d; } + # MCP server (streamable-http) for agent clients + location /mcp { + proxy_pass http://127.0.0.1:8090; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 3600s; + } + location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; diff --git a/templates/base.html b/templates/base.html index f5608dc..04065d4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,7 +6,7 @@ {% block title %}Clean Proxys{% endblock %} - +