atheris-mcp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arvane Holdings OÜ (atheris.ee)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ <!-- SOURCE OF TRUTH: this monorepo mcp/ directory. Any change to atheris-mcp.mjs
2
+ or this README must be pushed to the public mirror (github.com/atheris-ee/
3
+ atheris-mcp) and, if the server changed, published to npm as atheris-mcp. -->
4
+ # atheris-mcp
5
+
6
+ An [MCP](https://modelcontextprotocol.io) server for [Atheris](https://atheris.ee)
7
+ proxies — real-carrier 4G/5G mobile and residential IPs, per gigabyte. It gives an
8
+ agent a fully autonomous path to route traffic through a specific-country IP: check
9
+ live stock, get a ready-to-use proxy URL, and check remaining GB — all as tool calls.
10
+
11
+ Zero dependencies. One file. Node ≥ 18.
12
+
13
+ ## Tools
14
+
15
+ - **`atheris_stock`** — live proxy stock by country and pool (`mbl` = mobile,
16
+ `peer` = residential). No key needed. Call it first to pick a country with capacity.
17
+ - **`atheris_proxy_url`** — a ready `http://` or `socks5://` proxy URL for a
18
+ country/pool/session/rotation. Hand it straight to any HTTP client.
19
+ - **`atheris_usage`** — the key's remaining GB and expiry.
20
+
21
+ ## Setup
22
+
23
+ You need an Atheris access key (`pak_…`) from the [dashboard](https://atheris.ee/dashboard).
24
+
25
+ Straight from GitHub (no install):
26
+
27
+ ```jsonc
28
+ // Claude Desktop / Claude Code / Cursor / any MCP client
29
+ {
30
+ "mcpServers": {
31
+ "atheris": {
32
+ "command": "npx",
33
+ "args": ["-y", "github:atheris-ee/atheris-mcp"],
34
+ "env": { "ATHERIS_PROXY_KEY": "pak_your_key_here" }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Or from a local checkout:
41
+
42
+ ```jsonc
43
+ {
44
+ "mcpServers": {
45
+ "atheris": {
46
+ "command": "node",
47
+ "args": ["/path/to/atheris-mcp.mjs"],
48
+ "env": { "ATHERIS_PROXY_KEY": "pak_your_key_here" }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Env:
55
+ - `ATHERIS_PROXY_KEY` — your pak access key (required for `atheris_proxy_url`
56
+ and `atheris_usage`; `atheris_stock` works without it).
57
+ - `ATHERIS_API_BASE` — defaults to `https://atheris.ee/api/v1`. Your key is sent
58
+ as a Bearer token to whatever host this names, so only point it at a host you
59
+ trust.
60
+
61
+ The key is the same credential used as the proxy password — treat it like one.
62
+ Keep it in `env`, not in command args, and rotate it from the dashboard if it
63
+ leaks. The server talks only to `atheris.ee` (or your `ATHERIS_API_BASE`) and
64
+ collects no telemetry.
65
+
66
+ **The `atheris_proxy_url` result is also a secret.** Both its `proxy_url` and
67
+ `password` fields embed your pak key — treat the tool's output like the key
68
+ itself: don't log it or paste it into shared transcripts.
69
+
70
+ ## Session rule (important)
71
+
72
+ Each `atheris_proxy_url` call takes a `session` id. **The same session id returns the
73
+ same exit IP.** For many parallel identities (scraping distinct hosts, running many
74
+ antidetect profiles), give each identity its own session id — reusing one sticky
75
+ session across many pins them all to a single exit. Use `rotation: "hard"` if you want
76
+ a fresh exit on every connection instead.
77
+
78
+ ## The underlying HTTP API
79
+
80
+ The server is a thin wrapper over the Atheris JSON API (bearer-authed with your pak key):
81
+
82
+ - `GET /api/v1/stock`
83
+ - `GET /api/v1/proxy?country=us&pool=mbl&session=<id>&rotation=sticky&protocol=http`
84
+ - `GET /api/v1/usage`
85
+
86
+ See <https://atheris.ee/llms-full.txt> for the full agent reference, including
87
+ buying access programmatically.
88
+
89
+ ## License
90
+
91
+ MIT © [Arvane Holdings OÜ](https://atheris.ee)
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Atheris MCP server — exposes the Atheris proxy API as MCP tools so any
4
+ * MCP-capable agent can pick a country with live stock, get a ready proxy URL,
5
+ * and check its remaining GB. Zero dependencies (JSON-RPC 2.0 over stdio, node
6
+ * global fetch).
7
+ *
8
+ * Env:
9
+ * ATHERIS_PROXY_KEY your pak_ access key (required for proxy_url + usage)
10
+ * ATHERIS_API_BASE default https://atheris.ee/api/v1
11
+ *
12
+ * Run: ATHERIS_PROXY_KEY=pak_... node mcp/atheris-mcp.mjs
13
+ */
14
+ const API = (process.env.ATHERIS_API_BASE || "https://atheris.ee/api/v1").replace(/\/$/, "");
15
+ const KEY = process.env.ATHERIS_PROXY_KEY || "";
16
+ const PROTOCOL = "2025-06-18";
17
+ const UA = "atheris-mcp/0.1.1";
18
+ const TIMEOUT_MS = 20_000;
19
+
20
+ const TOOLS = [
21
+ {
22
+ name: "atheris_stock",
23
+ description:
24
+ "List live Atheris proxy stock by country and pool (mbl = mobile 4G/5G, peer = residential). No key required. Call this before atheris_proxy_url to pick a country that has capacity.",
25
+ inputSchema: { type: "object", properties: {} },
26
+ },
27
+ {
28
+ name: "atheris_proxy_url",
29
+ description:
30
+ "Get a ready-to-use Atheris proxy URL for a country. Returns an http:// or socks5:// URL you can hand to any HTTP client. Requires ATHERIS_PROXY_KEY. NOTE: the returned proxy_url and password embed your pak_ key — treat the result as a secret; do not log it or echo it into transcripts.",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ country: { type: "string", description: "lowercase ISO code, e.g. us, gb, nl, br" },
35
+ pool: { type: "string", enum: ["mbl", "peer"], description: "mbl=mobile, peer=residential (default mbl)" },
36
+ session: { type: "string", description: "session id [a-z0-9_]{1,64}; SAME id = SAME exit IP. Use a distinct id per identity." },
37
+ rotation: { type: "string", description: "sticky | sticky-strict | auto5 | auto10 | auto20 | auto30 | auto60 | hard | ondemand (default sticky)" },
38
+ protocol: { type: "string", enum: ["http", "socks5"], description: "default http" },
39
+ carrier: { type: "string", description: "optional mobile carrier, e.g. tmobile, orange" },
40
+ city: { type: "string", description: "optional city, lowercase (availability varies by country/pool)" },
41
+ },
42
+ required: ["country"],
43
+ },
44
+ },
45
+ {
46
+ name: "atheris_usage",
47
+ description: "Check the access key's remaining GB and expiry. Requires ATHERIS_PROXY_KEY.",
48
+ inputSchema: { type: "object", properties: {} },
49
+ },
50
+ ];
51
+
52
+ async function apiGet(path, auth) {
53
+ const headers = { "user-agent": UA };
54
+ if (auth) headers.authorization = `Bearer ${KEY}`;
55
+ let res;
56
+ try {
57
+ res = await fetch(`${API}${path}`, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
58
+ } catch (e) {
59
+ // Never let an upstream hang or network error wedge the MCP client — a
60
+ // tool call must always resolve. TimeoutError fires at TIMEOUT_MS.
61
+ const reason = e?.name === "TimeoutError" ? `timed out after ${TIMEOUT_MS}ms` : (e?.message ?? String(e));
62
+ return { ok: false, status: 0, json: { error: `request failed: ${reason}` } };
63
+ }
64
+ const text = await res.text();
65
+ let json;
66
+ try {
67
+ json = JSON.parse(text);
68
+ } catch {
69
+ json = { error: `non-JSON response (${res.status})`, body: text.slice(0, 200) };
70
+ }
71
+ return { ok: res.ok, status: res.status, json };
72
+ }
73
+
74
+ async function callTool(name, args = {}) {
75
+ if ((name === "atheris_proxy_url" || name === "atheris_usage") && !KEY) {
76
+ return { isError: true, text: "ATHERIS_PROXY_KEY is not set — required for this tool." };
77
+ }
78
+ if (name === "atheris_stock") {
79
+ const r = await apiGet("/stock", false);
80
+ return { isError: !r.ok, text: JSON.stringify(r.json, null, 2) };
81
+ }
82
+ if (name === "atheris_usage") {
83
+ const r = await apiGet("/usage", true);
84
+ return { isError: !r.ok, text: JSON.stringify(r.json, null, 2) };
85
+ }
86
+ if (name === "atheris_proxy_url") {
87
+ const q = new URLSearchParams();
88
+ for (const k of ["country", "pool", "session", "rotation", "protocol", "carrier", "city"]) {
89
+ if (args[k]) q.set(k, String(args[k]));
90
+ }
91
+ const r = await apiGet(`/proxy?${q.toString()}`, true);
92
+ return { isError: !r.ok, text: JSON.stringify(r.json, null, 2) };
93
+ }
94
+ return { isError: true, text: `unknown tool: ${name}` };
95
+ }
96
+
97
+ function send(msg) {
98
+ process.stdout.write(JSON.stringify(msg) + "\n");
99
+ }
100
+ const ok = (id, result) => send({ jsonrpc: "2.0", id, result });
101
+ const err = (id, code, message) => send({ jsonrpc: "2.0", id, error: { code, message } });
102
+
103
+ async function handle(msg) {
104
+ const { id, method, params } = msg;
105
+ if (method === "initialize") {
106
+ return ok(id, {
107
+ protocolVersion: params?.protocolVersion || PROTOCOL,
108
+ capabilities: { tools: {} },
109
+ serverInfo: { name: "atheris-mcp", version: "0.1.1" },
110
+ });
111
+ }
112
+ if (method === "notifications/initialized" || method === "notifications/cancelled") return; // notification, no reply
113
+ if (method === "ping") return ok(id, {});
114
+ if (method === "tools/list") return ok(id, { tools: TOOLS });
115
+ if (method === "tools/call") {
116
+ const { name, arguments: args } = params || {};
117
+ try {
118
+ const r = await callTool(name, args);
119
+ return ok(id, { content: [{ type: "text", text: r.text }], isError: r.isError });
120
+ } catch (e) {
121
+ return ok(id, { content: [{ type: "text", text: `error: ${e?.message ?? e}` }], isError: true });
122
+ }
123
+ }
124
+ if (id !== undefined) return err(id, -32601, `method not found: ${method}`);
125
+ }
126
+
127
+ let buf = "";
128
+ const inFlight = new Set();
129
+ process.stdin.setEncoding("utf8");
130
+ process.stdin.on("data", (chunk) => {
131
+ buf += chunk;
132
+ let nl;
133
+ while ((nl = buf.indexOf("\n")) >= 0) {
134
+ const line = buf.slice(0, nl).trim();
135
+ buf = buf.slice(nl + 1);
136
+ if (!line) continue;
137
+ let msg;
138
+ try {
139
+ msg = JSON.parse(line);
140
+ } catch {
141
+ continue;
142
+ }
143
+ const p = handle(msg).catch(() => {});
144
+ inFlight.add(p);
145
+ p.finally(() => inFlight.delete(p));
146
+ }
147
+ });
148
+ // Drain in-flight tool calls before exiting — a client (or one-shot pipe)
149
+ // that closes stdin right after a request must still get its reply.
150
+ process.stdin.on("end", () => {
151
+ Promise.allSettled([...inFlight]).then(() => process.exit(0));
152
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "atheris-mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for Atheris proxies — live stock, ready proxy URLs, and usage, as tools for any MCP-capable agent.",
5
+ "type": "module",
6
+ "bin": {
7
+ "atheris-mcp": "./atheris-mcp.mjs"
8
+ },
9
+ "files": [
10
+ "atheris-mcp.mjs",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/atheris-ee/atheris-mcp.git"
20
+ },
21
+ "homepage": "https://atheris.ee",
22
+ "mcpName": "io.github.atheris-ee/atheris-mcp",
23
+ "keywords": [
24
+ "mcp",
25
+ "mcp-server",
26
+ "proxy",
27
+ "mobile-proxy",
28
+ "residential-proxy",
29
+ "ai-agents",
30
+ "atheris"
31
+ ]
32
+ }