mcp-xray-pilot
v0.15.2
Published
MCP server for xray-core: offline docs cache (auto-refresh), config validation, REALITY/XTLS lint, anti-DPI recipes, GitHub research from XTLS repos.
Downloads
181
Maintainers
Readme
mcp-xray-pilot
Русская версия ниже / Russian version below
A Model Context Protocol (MCP) server that gives an LLM offline access to the official xray-core documentation, plus deep structural validation, best-practice lint, a protocol/transport/security compatibility matrix, a full v2fly geosite catalogue (~1500 categories), an alternative-stack suggester, REALITY keypair / shortId generators, a live SNI target validator, a curated SNI suggester per exit-country and a multi-config merge helper.
What it does
- Bundles ~60 docs pages from the upstream
XTLS/Xray-docs-nextrepo as raw markdown (no html→md mess). - Refreshes on demand:
xray_fetch_topictries the network first and silently overwrites the bundled cache on success. If you're offline (or upstream is down), it falls back to the packaged copy and surfaces awarningfield. - Searches the corpus with a tiny title/body relevance scorer.
- Validates xray JSON config: required top-level fields, per-protocol
Zod schemas (vless / vmess / trojan / shadowsocks / socks / http /
wireguard / hysteria / freedom / blackhole / dns / loopback / dokodemo /
tun), per-transport
*Settingsschemas (raw / xhttp / grpc / ws / mkcp / httpupgrade / hysteria), TLS / REALITY security blocks, routing tag cross-references. - Lints ~22 best-practice rules: VLESS
decryption: "none", REALITY pubkey/shortId/target syntax, XTLS vision flow compatibility, TLS fingerprint enum, ALPN collisions, geosite/geoip typo catcher, protocol × transport × security incompatibilities,xhttp.pathleading slash,geoip:privateblock rule, sniffing on 80/443, DNS-over-proxy leaks on.rudirect routing (v0.12),geosite:categories absent from xray-core releasegeosite.dat(v0.12) etc. - Geo catalogue: search ~1500 known geoip/geosite tags by substring (full
v2fly/domain-list-community catalogue, hydrated from
data/geocatalogue.json). - REALITY toolbelt:
xray_generate_reality_keypair(drop-in replacement forxray x25519),xray_generate_short_ids(cryptographically random, legacy-empty prefix),xray_validate_sni_target(live TLS 1.3 + h2 probe),xray_suggest_sni_for_country(curated REALITY fronts per exit-country). - Compares protocols: side-by-side table of vless/vmess/trojan/ss/ hysteria2/wireguard on transports, security, anti-DPI, mobile, battery.
- Recommends a stack for a stated goal (anti-DPI in RU/IR/CN, low latency, mobile battery, high throughput, stealth-CDN, getting started).
- Merges configs: joins inbounds/outbounds/routing.rules from N JSON configs, auto-resolves tag collisions, warns on port collisions.
Tools
| Tool | What it does |
| -------------------------- | ------------------------------------------------------------------------------------- |
| xray_list_topics | List doc topics, grouped by category. Use first to discover slugs. |
| xray_fetch_topic | Fetch one topic as markdown. Network → fall back to bundled cache → update cache. |
| xray_search | Full-text search over all cached docs. Returns ranked hits + snippets. |
| xray_validate_config | Structural+schema validation of an xray JSON config (Zod under the hood). |
| xray_lint | ~22 best-practice lint rules. Returns issues with severity, rule id, JSON-pointer. |
| xray_geo_search | Substring search over the embedded geosite/geoip catalogue (~1500 tags). |
| xray_diff_protocols | Side-by-side feature table for two protocols. |
| xray_suggest_alternative | Recommend protocol+transport+security for a goal (anti-DPI / battery / latency / …). |
| xray_generate_short_ids | Cryptographically random REALITY shortIds (default [4,8,16] bytes, legacy empty prefix). |
| xray_generate_reality_keypair | Fresh REALITY X25519 keypair, base64url 43 chars — drop-in for xray x25519. |
| xray_validate_sni_target | Live TLS 1.3 + ALPN h2 + HTTP probe of a candidate REALITY target host. |
| xray_test_reality_live | Spin up real local xray server+client, run a full REALITY handshake against the target, probe HTTPS through the cascade. Strictly stronger than xray_validate_sni_target. |
| xray_whitelist_sni_candidates | Pull a public RU-traffic whitelist (default: hxehex/russia-mobile-internet-whitelist) and TLS-validate the top N hosts as REALITY SNI front candidates. Returns ranked, sorted by ok desc, latency asc. |
| xray_suggest_sni_for_country | Curated REALITY SNI/target hosts per exit-country (DE/PL/NL/FR/LV/SE/FI/US/UK/JP/SG/AU/CA). |
| xray_merge_configs | Merge N xray configs with tag-collision resolution and conflict warnings. |
| xray_github_search | Search issues/PRs/discussions across XTLS GitHub repos (Xray-core/REALITY/docs). |
| xray_github_fetch_issue | Fetch one issue/PR/discussion with full body + top comments. |
| xray_refresh_cache | Bulk re-fetch cached docs (scope: all/stale/category). Optional discover for new upstream slugs. Optional refresh_geocatalogue: true to also re-pull the v2fly category list. |
Total: 18 tools.
Quick toolbelt
Generate a REALITY keypair (server-side priv + client-side pub):
// xray_generate_reality_keypair {}
{
"privateKey": "MNCibkT-h5bCF6iknJG0rJdHdfUjT7VugHgSX9BRWUY",
"publicKey": "ffvf0eNwnLhgK-axt3rajJIAoHKv0rX4xkw5KyImn38",
"note": "Server: paste privateKey into inbound realitySettings.privateKey. Clients: paste publicKey into outbound realitySettings.publicKey. Format matches `xray x25519` output exactly."
}Live-check a candidate REALITY SNI:
// xray_validate_sni_target { "host": "www.onet.pl" }
{
"host": "www.onet.pl", "port": 443, "ok": true,
"tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200,
"cert_subject": "www.onet.pl", "cert_san_count": 4,
"latency_ms": 312, "issues": []
}Live REALITY handshake (catches what xray_validate_sni_target cannot):
// xray_test_reality_live { "target_host": "2gis.ru" }
{
"ok": true,
"target": "2gis.ru:443",
"reality_handshake_complete": true,
"client_received_real_cert": false,
"http_probe_status": 200,
"latency_ms": 824,
"issues": [],
"used_keypair": { "privateKey": "...", "publicKey": "...", "shortId": "a1b2c3d4" },
"cached": false,
"cached_at": "2026-05-06T12:30:00.000Z"
}Verdicts are persisted to
data/reality-verdicts.json(LRU cap 50, TTL 24h, keyhost:port). Subsequent calls with the same target return instantly withcached: true. Passforce_refresh: trueto bypass the cache and rerun xray.
Compare a list of candidates in one shot — runs sequentially, each entry hits
the cache, results are sorted by ok desc, latency_ms asc:
// xray_test_reality_live {
// "multi_targets": ["2gis.ru", "yandex.ru", "vk.com", "mail.ru"]
// }
{
"results": [
{ "target": "2gis.ru:443", "ok": true, "latency_ms": 824, "cached": true, "cached_at": "..." },
{ "target": "mail.ru:443", "ok": true, "latency_ms": 1102, "cached": false, "cached_at": "..." },
{ "target": "yandex.ru:443", "ok": false, "latency_ms": 0, "cached": false, "cached_at": "...", "issues": ["client received a real certificate ..."] },
{ "target": "vk.com:443", "ok": false, "latency_ms": 0, "cached": false, "cached_at": "...", "issues": ["target unreachable"] }
],
"summary": { "ok_count": 2, "total": 4 }
}Pull a public RU-traffic whitelist and rank live SNI candidates for an inbound RU-relay node:
// xray_whitelist_sni_candidates { "max_candidates": 10 }
{
"source_url": "https://raw.githubusercontent.com/hxehex/russia-mobile-internet-whitelist/main/whitelist.txt",
"fetched_at": "2026-05-06T12:34:00.000Z",
"total_domains": 412,
"tested": 10,
"candidates": [
{ "host": "ya.ru", "ok": true, "tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200, "latency_ms": 41, "cert_subject": "ya.ru", "issues": [] },
{ "host": "vk.com", "ok": true, "tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200, "latency_ms": 78, "cert_subject": "*.vk.com", "issues": [] },
{ "host": "ozon.ru", "ok": false, "tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200, "latency_ms": 134, "cert_subject": "*.ozon.ru", "issues": [] }
],
"summary": { "ok_count": 8, "failed_count": 2 },
"cache": { "used": false, "age_seconds": null },
"notes": ["latency_ms is measured from the MCP host (your laptop), not from the relay node — re-test from the node for geo-accurate values"]
}The whitelist body is cached on disk in
data/whitelist-cache.json(default TTL 24h, override viacache_ttl_hours). Verdicts are NOT cached — every call re-probes its slice live so latency numbers stay fresh. NB: the probe runs from your laptop, not from the РФ relay; for geo-accurate latency, usexray_test_reality_livefrom the actual node (or ssh + curl on it).
It also exposes a single MCP resource: xray://docs/index — the raw
_index.json of cached topics.
Examples
Example 1 — lint a broken cascade config
Prompt:
Lint this xray config and tell me what's wrong.
{ "inbounds":[{"tag":"in1","port":443,"protocol":"vless","settings":{"clients":[{"id":"00000000-0000-4000-8000-000000000000","flow":"xtls-rprx-vision"}]},"streamSettings":{"network":"ws","security":"reality","realitySettings":{"target":"yandex.com","privateKey":"short","shortIds":["zz"],"serverNames":["yandex.com"],"fingerprint":"netscape"}}}], "outbounds":[{"tag":"out","protocol":"freedom"}], "routing":{"rules":[{"type":"field","outboundTag":"missing","domain":["geosite:tinkoff-bank"]}]} }
xray_lint response (excerpt):
{
"summary": { "error_count": 6, "warn_count": 5, "info_count": 2 },
"issues": [
{ "rule": "reality_pubkey_format", "severity": "error",
"path": "/inbounds/0/streamSettings/realitySettings/privateKey",
"message": "REALITY privateKey must be 43 base64url chars, got 'short'" },
{ "rule": "reality_shortid_format", "severity": "error",
"path": "/inbounds/0/streamSettings/realitySettings/shortIds/0",
"message": "shortIds[0] 'zz' must be hex (0..16 chars, even length)" },
{ "rule": "flow_requires_specific_transport", "severity": "error",
"path": "/inbounds/0/streamSettings/network",
"message": "flow=xtls-rprx-vision requires raw/tcp transport, got 'ws'" },
{ "rule": "routing_dangling_outbound", "severity": "error",
"path": "/routing/rules/0/outboundTag",
"message": "outboundTag 'missing' does not match any outbound tag" },
{ "rule": "tls_fingerprint_enum", "severity": "warn",
"path": "/inbounds/0/streamSettings/realitySettings/fingerprint",
"message": "'netscape' is not a known fingerprint (chrome/firefox/safari/ios/android/edge/360/qq/random/randomized)" },
{ "rule": "geo_unknown_category", "severity": "warn",
"path": "/routing/rules/0/domain/0",
"message": "geosite:tinkoff-bank is not in the bundled catalogue (typo? try geosite:category-ru)" }
]
}Example 2 — research RKN bypass via GitHub
Prompt:
Find recent xray issues about RKN/TSPU and suggest an anti-DPI stack for Russia.
Step 1 — xray_github_search:
// args
{ "query": "RKN", "type": "issue", "repo": "all", "sort": "updated" }
// response (excerpt)
{
"total_count": 47,
"items": [
{ "repo": "XTLS/Xray-core", "number": 5747, "state": "open",
"title": "REALITY blocked by TSPU after recent RKN update",
"comments": 38, "reactions": { "+1": 21 },
"snippet": "Starting last week our REALITY inbound on :443 stops responding after ~30s of traffic from RU clients. xhttp+REALITY survives longer than raw+vision…" },
{ "repo": "XTLS/Xray-core", "number": 5332, "state": "closed",
"title": "RKN: shortId enumeration probe",
"comments": 22,
"snippet": "TSPU appears to brute-force shortIds. Recommendation: rotate, keep list >=4 entries, do not include empty string…" }
]
}Step 2 — xray_suggest_alternative:
// args
{ "goal": "anti-dpi-russia" }
// response (excerpt)
{
"recommendation": {
"protocol": "vless",
"transport": "xhttp",
"security": "reality",
"flow": null,
"rationale": [
"xhttp survives TSPU active probing better than raw/tcp+vision in 2025",
"REALITY hides SNI; pick a target popular in RU (yandex.com, mail.ru)",
"Rotate shortIds[] (>=4 entries, hex, no empty string)",
"Keep packet padding default; do not enable kcp on top of REALITY"
]
},
"see_also": ["xray_fetch_topic transports/xhttp", "xray_fetch_topic features/reality"]
}Install
npm i -g mcp-xray-pilotOr run from source:
git clone https://github.com/beekamai/mcp-xray-pilot.git
cd mcp-xray-pilot
npm install
npm run build
npm run fetch-docs # fills data/docs/ if you cloned without itAdd to Claude Code
claude mcp add xray-pilot --scope user -- npx -y mcp-xray-pilotWith a GitHub PAT (raises xray_github_* rate limit, enables discussions):
claude mcp add xray-pilot --scope user --env GITHUB_TOKEN=ghp_xxx -- npx -y mcp-xray-pilotOr, from a local clone:
claude mcp add xray-pilot --scope user -- node /absolute/path/to/mcp-xray-pilot/dist/index.jsAdd to Cursor / Windsurf / Cline
{
"mcpServers": {
"xray-pilot": {
"command": "npx",
"args": ["-y", "mcp-xray-pilot"]
}
}
}Offline cache vs. online refresh
The data/docs/ directory ships with the package. Each call to
xray_fetch_topic:
- If
force_offline=true→ reads only the packaged copy. - Otherwise → tries the upstream raw markdown URL (10s timeout). On HTTP
200, the response body replaces the on-disk markdown and the index
entry's
fetched_atis updated. Subsequent calls in the same process serve from in-memory cache. - On any network failure → falls back to the packaged copy, returning the
markdown plus
warning: "network fetch failed: …".
To refresh everything in bulk, run npm run fetch-docs -- --refresh. To
discover newly-added pages upstream without writing them: npm run
fetch-docs -- --discover.
Keeping cache fresh
There are three complementary ways to keep data/docs/ aligned with
upstream:
- On-demand per page — every
xray_fetch_topiccall already tries the network first and silently overwrites the on-disk copy on success. No action needed. - Bulk via MCP tool — call
xray_refresh_cachefrom the LLM:{ "scope": "stale", "max_age_days": 30 }(default) re-fetches only entries older than N days.{ "scope": "all" }re-fetches every page (~60).{ "scope": "category", "category": "transports" }restricts to one category.- Add
"discover": trueto also report slugs that exist upstream but are missing fromDOCS_CATALOGUEinsrc/docs.ts.
- CI weekly cron —
.github/workflows/refresh-docs.ymlrunsnpm run fetch-docs -- --refreshevery Monday 06:00 UTC and opens a PR if anything changed (also triggerable manually viaworkflow_dispatch).
Optional GITHUB_TOKEN env variable
xray_github_search and xray_github_fetch_issue work anonymously, but
the GitHub API caps unauthenticated requests at 60/hour. Setting
GITHUB_TOKEN to any classic or fine-grained PAT (no scopes needed for
public repos) raises the limit to 5000/hour and additionally enables
the discussions endpoint (GraphQL), which has no anonymous access.
export GITHUB_TOKEN=ghp_xxx # Linux / macOS
$env:GITHUB_TOKEN = "ghp_xxx" # PowerShellWhen X-RateLimit-Remaining drops below 10, the tool surfaces an inline
warning in the response.
Security model
This package runs entirely in the MCP host's process and the only
"interesting" thing it does on disk/network is xray_test_reality_live,
which downloads the official xray-core binary from
XTLS/Xray-core releases
into ~/.cache/mcp-xray-pilot/xray-bin/ and spawns it locally on
ephemeral ports.
Approvals. There is no in-package permission prompt by design — all
tool invocations are gated by the MCP host (Claude Code / Claude Desktop
/ Cursor). Each host has its own permission UX (per-call confirm,
allowlist, etc.). Pin a specific version in your MCP config
([email protected], not mcp-xray-pilot) so a malicious npm
release cannot silently roll in.
Integrity of the xray binary. Since v0.15:
The matching
<asset>.zip.dgstfrom the same GitHub release is downloaded alongside the zip, and the zip is verified against itsSHA2-256=line before extraction. Mismatched payloads are renamed toxray.rejected-<timestamp>.zipfor inspection and the tool aborts.If you want a stronger guarantee than "trust whatever XTLS ships right now", set
XRAY_PILOT_PINNED_HASHto a known SHA-256 — the.dgstis then ignored and the pinned value is enforced. This defends against a hypothetical compromised XTLS release that ships a matching.dgst.# Linux / macOS — read the hash once, then pin it. curl -sL https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-64.zip.dgst \ | awk '/^SHA2-256=/ {print $2}' export XRAY_PILOT_PINNED_HASH=23cd9af9...$env:XRAY_PILOT_PINNED_HASH = "23cd9af9..."
What this does NOT do. XTLS does not GPG-sign .dgst files, so an
attacker with write access to the releases page can ship a coherent
zip+dgst pair. The pinned-hash path is your defense against that. The
package does not sandbox the spawned xray binary — it runs with the
permissions of the MCP host process. If you need stronger isolation,
run the MCP host inside a container or a dedicated user account.
GITHUB_TOKEN. Optional, used only by xray_github_search /
xray_github_fetch_issue. A fine-grained PAT with read-only public
repo scope is recommended over classic PATs. The token is read from
the process env at call time and never persisted by the package.
Roadmap
See ROADMAP.md. All v0.1–v0.11 milestones are checked off.
License
MIT.
mcp-xray-pilot (RU)
MCP-сервер, дающий LLM офлайн-доступ к официальной документации xray-core, плюс глубокую валидацию по схемам, lint best-practice, матрицу совместимости протоколов/транспортов/security, каталог geosite/geoip, рекомендатор альтернативного стека и helper для слияния конфигов.
Что делает
- Упаковывает ~60 страниц документации из upstream-репозитория
XTLS/Xray-docs-nextкак raw markdown (без html→md мусора). - Обновляется по запросу:
xray_fetch_topicсначала идёт в сеть и при успехе молча перезаписывает упакованный кеш. Если оффлайн (или upstream лёг), возвращает упакованную копию и выставляетwarning. - Поиск по корпусу простым title/body relevance scorer.
- Валидирует xray JSON: обязательные top-level поля, per-protocol
Zod-схемы (vless / vmess / trojan / shadowsocks / socks / http /
wireguard / hysteria / freedom / blackhole / dns / loopback / dokodemo /
tun), per-transport
*Settings(raw / xhttp / grpc / ws / mkcp / httpupgrade / hysteria), security блоки TLS/REALITY, routing tag cross-references. - Lint ~22 правил: VLESS
decryption: "none", REALITY pubkey/shortId/ target syntax, XTLS vision flow compatibility, TLS fingerprint enum, ALPN collisions, geo typo catcher, protocol × transport × security несовместимости,xhttp.pathслеш,geoip:privateblock, sniffing на 80/443, утечка DNS через прокси ломает.rudirect routing (v0.12),geosite:категории, которых нет в xray-core releasegeosite.dat(v0.12) и т.д. - Geo catalogue: поиск по ~1500 известным geoip/geosite тегам (полный
v2fly/domain-list-community каталог, гидратируется из
data/geocatalogue.json). - REALITY toolbelt:
xray_generate_reality_keypair(drop-in заменаxray x25519),xray_generate_short_ids(криптослучайные, с empty-prefix для легаси),xray_validate_sni_target(live TLS 1.3 + h2 проба),xray_suggest_sni_for_country(курируемые REALITY-фронты по стране exit'а). - Сравнение протоколов: таблица vless/vmess/trojan/ss/hysteria2/ wireguard по transports, security, anti-DPI, mobile, battery.
- Рекомендует стек под цель (anti-DPI в РФ/Иране/КНР, low-latency, mobile-battery, high-throughput, stealth-CDN, getting started).
- Сливает конфиги: объединяет inbounds/outbounds/routing.rules из N JSON конфигов, авто-резолвит коллизии тегов, варнит на коллизиях портов.
Тулы
| Тул | Что делает |
| -------------------------- | --------------------------------------------------------------------------------------- |
| xray_list_topics | Список тем по категориям. Дёргать первым. |
| xray_fetch_topic | Получить тему как markdown. Сеть → fallback на кеш → обновление кеша. |
| xray_search | Полнотекстовый поиск по докам. Хиты + сниппеты. |
| xray_validate_config | Структурная валидация + Zod-схемы по протоколу/transport/security. |
| xray_lint | ~20 правил best-practice. Issues с severity, rule id, JSON-pointer. |
| xray_geo_search | Поиск по embedded каталогу geosite/geoip по подстроке (~1500 тегов). |
| xray_diff_protocols | Side-by-side таблица фич двух протоколов. |
| xray_suggest_alternative | Рекомендация protocol+transport+security под цель. |
| xray_generate_short_ids | Криптослучайные REALITY shortIds (default [4,8,16] байт, с empty-prefix для легаси). |
| xray_generate_reality_keypair | Свежая X25519 пара REALITY, base64url 43 chars — drop-in для xray x25519. |
| xray_validate_sni_target | Live проба TLS 1.3 + ALPN h2 + HTTP кандидата на REALITY target. |
| xray_test_reality_live | Поднимает локальную пару xray (server+client), реально гоняет REALITY handshake к target, probe HTTPS сквозь каскад. Строго сильнее xray_validate_sni_target. |
| xray_whitelist_sni_candidates | Тянет публичный whitelist РФ-трафика (default: hxehex/russia-mobile-internet-whitelist), TLS-валидирует топ-N как REALITY SNI-кандидаты. Сортировка ok desc, latency asc. |
| xray_suggest_sni_for_country | Курируемые REALITY-фронты по стране exit'а (DE/PL/NL/FR/LV/SE/FI/US/UK/JP/SG/AU/CA). |
| xray_merge_configs | Слить N конфигов с разрешением коллизий тегов. |
| xray_github_search | Поиск issues/PR/discussions по XTLS GitHub репозиториям. |
| xray_github_fetch_issue | Получить одну issue/PR/discussion с полным body + топ комментариев. |
| xray_refresh_cache | Bulk перезатяжка кеша доков (scope: all/stale/category). Опц. discover + refresh_geocatalogue: true (заодно перетянет v2fly категории). |
Всего: 18 тулов.
Quick toolbelt
Сгенерить REALITY keypair (priv для сервера, pub для клиентов):
// xray_generate_reality_keypair {}
{
"privateKey": "MNCibkT-h5bCF6iknJG0rJdHdfUjT7VugHgSX9BRWUY",
"publicKey": "ffvf0eNwnLhgK-axt3rajJIAoHKv0rX4xkw5KyImn38",
"note": "Format matches `xray x25519` output exactly."
}Live-проверить кандидата на REALITY SNI:
// xray_validate_sni_target { "host": "www.onet.pl" }
{
"host": "www.onet.pl", "port": 443, "ok": true,
"tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200,
"cert_subject": "www.onet.pl", "cert_san_count": 4,
"latency_ms": 312, "issues": []
}⚠️ Проба идёт с локальной машины, где запущен
mcp-xray-pilot. Для решения «работает ли SNI из РФ» — гоняй с РФ-IP отдельно.
Реальный REALITY handshake (ловит то, что xray_validate_sni_target пропускает):
// xray_test_reality_live { "target_host": "2gis.ru" }
{
"ok": true,
"target": "2gis.ru:443",
"reality_handshake_complete": true,
"client_received_real_cert": false,
"http_probe_status": 200,
"latency_ms": 824,
"issues": [],
"used_keypair": { "privateKey": "...", "publicKey": "...", "shortId": "a1b2c3d4" },
"cached": false,
"cached_at": "2026-05-06T12:30:00.000Z"
}При первом вызове скачивает xray-binary в
~/.cache/mcp-xray-pilot/xray-bin/(~30MB), дальше переиспользует. Если REALITY несовместим с target (какoutlook.live.comилиwww.ozon.ru— реальные кейсы Flare VPN),client_received_real_cert: trueиok: false.
Вердикты пишутся на диск в
data/reality-verdicts.json(LRU 50, TTL 24h, ключhost:port). Повторный вызов на тот же таргет возвращается мгновенно сcached: true. Чтобы прогнать заново —force_refresh: true.
Сравнить пачку кандидатов одним вызовом — запускаются последовательно, каждый через кэш, сорт ok desc, latency_ms asc:
// xray_test_reality_live {
// "multi_targets": ["2gis.ru", "yandex.ru", "vk.com", "mail.ru"]
// }
{
"results": [
{ "target": "2gis.ru:443", "ok": true, "latency_ms": 824, "cached": true },
{ "target": "mail.ru:443", "ok": true, "latency_ms": 1102, "cached": false },
{ "target": "yandex.ru:443", "ok": false, "issues": ["client received a real certificate ..."] },
{ "target": "vk.com:443", "ok": false, "issues": ["target unreachable"] }
],
"summary": { "ok_count": 2, "total": 4 }
}Затянуть публичный whitelist РФ-трафика и проранжировать кандидатов на REALITY SNI для inbound РФ-relay-ноды:
// xray_whitelist_sni_candidates { "max_candidates": 10 }
{
"source_url": "https://raw.githubusercontent.com/hxehex/russia-mobile-internet-whitelist/main/whitelist.txt",
"fetched_at": "2026-05-06T12:34:00.000Z",
"total_domains": 412,
"tested": 10,
"candidates": [
{ "host": "ya.ru", "ok": true, "tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200, "latency_ms": 41, "cert_subject": "ya.ru", "issues": [] },
{ "host": "vk.com", "ok": true, "tls_version": "TLSv1.3", "alpn": "h2", "http_status": 200, "latency_ms": 78, "cert_subject": "*.vk.com", "issues": [] }
],
"summary": { "ok_count": 8, "failed_count": 2 },
"cache": { "used": false, "age_seconds": null },
"notes": ["latency_ms is measured from the MCP host (your laptop), not from the relay node — re-test from the node for geo-accurate values"]
}Тело whitelist кешируется на диск в
data/whitelist-cache.json(TTL по умолчанию 24h, переопределение черезcache_ttl_hours). Verdicts НЕ кешируются — каждый вызов делает свежие пробы на своём срезе. NB: latency меряется с тачки где запущен MCP, а НЕ с РФ-relay-ноды; для гео-релевантной задержки используйxray_test_reality_liveпрямо с ноды (или ssh + curl на ней).
Также один MCP ресурс: xray://docs/index.
Примеры
Пример 1 — линт сломанного каскадного конфига
Промпт:
Прогони линт по этому xray конфигу и скажи что не так.
{ "inbounds":[{"tag":"in1","port":443,"protocol":"vless","settings":{"clients":[{"id":"00000000-0000-4000-8000-000000000000","flow":"xtls-rprx-vision"}]},"streamSettings":{"network":"ws","security":"reality","realitySettings":{"target":"yandex.com","privateKey":"short","shortIds":["zz"],"serverNames":["yandex.com"],"fingerprint":"netscape"}}}], "outbounds":[{"tag":"out","protocol":"freedom"}], "routing":{"rules":[{"type":"field","outboundTag":"missing","domain":["geosite:tinkoff-bank"]}]} }
Ответ xray_lint (выжимка):
{
"summary": { "error_count": 6, "warn_count": 5, "info_count": 2 },
"issues": [
{ "rule": "reality_pubkey_format", "severity": "error",
"path": "/inbounds/0/streamSettings/realitySettings/privateKey",
"message": "REALITY privateKey must be 43 base64url chars, got 'short'" },
{ "rule": "reality_shortid_format", "severity": "error",
"path": "/inbounds/0/streamSettings/realitySettings/shortIds/0",
"message": "shortIds[0] 'zz' must be hex (0..16 chars, even length)" },
{ "rule": "flow_requires_specific_transport", "severity": "error",
"path": "/inbounds/0/streamSettings/network",
"message": "flow=xtls-rprx-vision requires raw/tcp transport, got 'ws'" },
{ "rule": "routing_dangling_outbound", "severity": "error",
"path": "/routing/rules/0/outboundTag",
"message": "outboundTag 'missing' does not match any outbound tag" },
{ "rule": "tls_fingerprint_enum", "severity": "warn",
"path": "/inbounds/0/streamSettings/realitySettings/fingerprint",
"message": "'netscape' is not a known fingerprint (chrome/firefox/safari/ios/android/edge/360/qq/random/randomized)" },
{ "rule": "geo_unknown_category", "severity": "warn",
"path": "/routing/rules/0/domain/0",
"message": "geosite:tinkoff-bank is not in the bundled catalogue (typo? try geosite:category-ru)" }
]
}Пример 2 — ресёрч обхода РКН через GitHub
Промпт:
Найди свежие xray issues про РКН/ТСПУ и предложи anti-DPI стек под Россию.
Шаг 1 — xray_github_search:
// args
{ "query": "RKN", "type": "issue", "repo": "all", "sort": "updated" }
// response (выжимка)
{
"total_count": 47,
"items": [
{ "repo": "XTLS/Xray-core", "number": 5747, "state": "open",
"title": "REALITY blocked by TSPU after recent RKN update",
"comments": 38, "reactions": { "+1": 21 },
"snippet": "Starting last week our REALITY inbound on :443 stops responding after ~30s of traffic from RU clients. xhttp+REALITY survives longer than raw+vision…" },
{ "repo": "XTLS/Xray-core", "number": 5332, "state": "closed",
"title": "RKN: shortId enumeration probe",
"comments": 22,
"snippet": "TSPU appears to brute-force shortIds. Recommendation: rotate, keep list >=4 entries, do not include empty string…" }
]
}Шаг 2 — xray_suggest_alternative:
// args
{ "goal": "anti-dpi-russia" }
// response (выжимка)
{
"recommendation": {
"protocol": "vless",
"transport": "xhttp",
"security": "reality",
"flow": null,
"rationale": [
"xhttp survives TSPU active probing better than raw/tcp+vision in 2025",
"REALITY hides SNI; pick a target popular in RU (yandex.com, mail.ru)",
"Rotate shortIds[] (>=4 entries, hex, no empty string)",
"Keep packet padding default; do not enable kcp on top of REALITY"
]
},
"see_also": ["xray_fetch_topic transports/xhttp", "xray_fetch_topic features/reality"]
}Установка
npm i -g mcp-xray-pilotИли из исходников:
git clone https://github.com/beekamai/mcp-xray-pilot.git
cd mcp-xray-pilot
npm install
npm run build
npm run fetch-docsПодключить к Claude Code
claude mcp add xray-pilot --scope user -- npx -y mcp-xray-pilotС GitHub PAT (поднимает rate-limit xray_github_*, включает discussions):
claude mcp add xray-pilot --scope user --env GITHUB_TOKEN=ghp_xxx -- npx -y mcp-xray-pilotИли из локального клона:
claude mcp add xray-pilot --scope user -- node /absolute/path/to/mcp-xray-pilot/dist/index.jsОфлайн-кеш vs онлайн-обновление
Папка data/docs/ едет в пакете. Каждый вызов xray_fetch_topic:
- При
force_offline=true→ читает только упакованную копию. - Иначе → пробует upstream raw URL (10s таймаут). При HTTP 200 ответ
перезаписывает markdown на диске,
fetched_atобновляется. Последующие вызовы в том же процессе отдаются из in-memory кеша. - При любой сетевой ошибке → fallback на упакованную копию, возвращает
markdown плюс
warning: "network fetch failed: …".
Bulk-обновление — npm run fetch-docs -- --refresh. Discover новых
страниц в upstream без записи — npm run fetch-docs -- --discover.
Поддержание кеша актуальным
Три способа держать data/docs/ в синхроне с upstream:
- На каждый запрос —
xray_fetch_topicсам ходит в сеть и при HTTP 200 перезаписывает on-disk копию. Ничего делать не надо. - Bulk через MCP-тул — позови
xray_refresh_cache:{ "scope": "stale", "max_age_days": 30 }(default) — только устаревшие старше N дней.{ "scope": "all" }— все ~60 страниц.{ "scope": "category", "category": "transports" }— одна категория."discover": true— дополнительно вернёт список slug'ов, которые появились upstream, но отсутствуют вDOCS_CATALOGUE(src/docs.ts).
- CI weekly cron —
.github/workflows/refresh-docs.ymlкаждый понедельник 06:00 UTC гоняетnpm run fetch-docs -- --refreshи открывает PR если что-то поменялось (естьworkflow_dispatchдля ручного триггера).
Опциональный GITHUB_TOKEN
xray_github_search и xray_github_fetch_issue работают анонимно, но
GitHub API лимитирует unauth запросы 60/час. Установка GITHUB_TOKEN
(любой classic или fine-grained PAT, для публичных репо scope не нужен)
поднимает лимит до 5000/час и дополнительно включает поиск/чтение
discussions (GraphQL, у него нет anon-доступа).
export GITHUB_TOKEN=ghp_xxx # Linux / macOS
$env:GITHUB_TOKEN = "ghp_xxx" # PowerShellКогда X-RateLimit-Remaining падает ниже 10, тул возвращает inline-warning
в ответе.
Модель безопасности
Пакет крутится внутри процесса MCP-хоста, и единственное «опасное»
действие на диск/сеть — xray_test_reality_live, который скачивает
официальный бинарь xray-core из
XTLS/Xray-core releases
в ~/.cache/mcp-xray-pilot/xray-bin/ и запускает его локально на
ephemeral портах.
Approvals. Встроенного permission-prompt'а в пакете нет — это by
design. Все тулколлы гейтятся MCP-хостом (Claude Code / Claude Desktop /
Cursor) — у каждого хоста свой UX (per-call confirm, allowlist и т.д.).
Пиньте конкретную версию в MCP-конфиге ([email protected], а не
просто mcp-xray-pilot), чтобы вредоносный npm-релиз не подкатился
тихо.
Целостность бинаря xray. Начиная с v0.15:
Рядом со скачиванием zip-а тянется
<asset>.zip.dgstиз того же GitHub-релиза, и до распаковки zip проверяется против строкиSHA2-256=. Если хэши не совпали — payload переименовывается вxray.rejected-<timestamp>.zipдля разбора, и тул abort'ится.Если хочется жёстче чем «доверяю тому что XTLS залил прямо сейчас» — задайте
XRAY_PILOT_PINNED_HASHс известным SHA-256. Тогда.dgstигнорируется, проверяется ваше захардкоженное значение. Это защищает от гипотетического compromised XTLS-релиза, где злоумышленник залил согласованную пару zip+dgst.# Linux / macOS — снять хэш один раз, потом пиннуть. curl -sL https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-64.zip.dgst \ | awk '/^SHA2-256=/ {print $2}' export XRAY_PILOT_PINNED_HASH=23cd9af9...$env:XRAY_PILOT_PINNED_HASH = "23cd9af9..."
Чего пакет НЕ делает. XTLS не GPG-подписывает .dgst, поэтому
атакер с правами на releases-страницу может выкатить согласованную пару
zip+dgst. Защита от этого — pinned-hash. Sandbox для запущенного xray
пакет не делает — бинарь работает с правами процесса MCP-хоста. Хотите
жёстче — запускайте MCP-хост в контейнере или под отдельным юзером.
GITHUB_TOKEN. Опционально, используется только
xray_github_search / xray_github_fetch_issue. Рекомендуется
fine-grained PAT с read-only scope для публичных репо вместо
classic PAT. Токен читается из env при вызове и пакетом не сохраняется.
Roadmap
См. ROADMAP.md — все вехи v0.1–v0.11 закрыты.
Лицензия
MIT.
