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 +21 -0
- package/README.md +91 -0
- package/atheris-mcp.mjs +152 -0
- package/package.json +32 -0
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)
|
package/atheris-mcp.mjs
ADDED
|
@@ -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
|
+
}
|