Repository navigation
MCP server
The panel's built-in MCP server for AI agents, how to connect a client, every tool, the plan and apply flow, and which changes wait for the owner.
The panel has a built-in Model Context Protocol (MCP) server, so an AI agent can read the fleet and make day-to-day changes. It uses the same API tokens and the same procedures as the admin API: the token's profile decides what the agent sees and may do, and every call is in the audit log. Nothing changes in one call: an agent plans a change, then applies it, and the riskiest changes wait for the owner's approval in the admin.
The HTTP endpoint below is available in the VPS edition. The Edge edition does not serve /mcp in phase 1; Edge support is planned for phase 2 behind a separate Worker service binding.
<admin URL>mcp
https://panel.example.com/<prefix>/mcp
- Streamable HTTP, stateless, JSON responses. It offers tools only: no resources, prompts or sampling.
- It exists only on the admin surface (the secret prefix, host or listener), never on the public side.
- Every request needs
Authorization: Bearer tk1_…and is checked again: a revoked or expired token stops at its next request. There is no session to take over. -
tools/listshows only the tools the token's profile may use.
Integrations → MCP shows the address and a ready snippet for each kind of client.
Create a token in Integrations → API tokens with the narrowest profile that works (see Token profiles). Then:
Claude Code (Streamable HTTP):
claude mcp add --transport http mistgate https://panel.example.com/<prefix>/mcp --header "Authorization: Bearer <token>"Any client that speaks Streamable HTTP:
{
"mcpServers": {
"mistgate": {
"type": "http",
"url": "https://panel.example.com/<prefix>/mcp",
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}Clients that can only start a command (for example Claude Desktop) use the stdio proxy, mistgate mcp, on your own computer. Save the token as the first line of a file only you can read, then:
{
"mcpServers": {
"mistgate": {
"command": "mistgate",
"args": ["mcp", "--url", "https://panel.example.com/<prefix>/", "--token-file", "<path-to-token-file>"]
}
}
}mistgate mcp --url <admin URL> --token-file <file> runs a local MCP server on stdin and stdout and forwards every message to the panel's endpoint. It decides nothing, caches nothing and knows no tool: the panel answers.
- The token is read only from the file's first line, never from the command line or the environment, and never printed. A warning is printed when others can read the file (not checked on Windows).
-
--urlmust behttps, except forlocalhostor a loopback address. Redirects are not followed, so the token goes nowhere else. - When the panel refuses the token (expired, revoked or the wrong profile) the proxy stops with that message.
-
--urland--token-filefall back toMISTGATE_URLandMISTGATE_TOKEN_FILE.
mistgate builds for Linux, macOS and Windows: go build ./cmd/mistgate makes the binary for the computer the agent runs on. See CLI.
| Limit | Value |
|---|---|
| Tool calls at a time, per token | 4 (more: 429 "at most 4 tool calls at a time per token") |
| Requests per minute | the token's own limit |
| Request body | 3 MiB |
| A tool result | 32 KiB; long lists are halved until they fit and marked as truncated |
| Time per call | 30 s for reads and plans, 90 s for an apply |
| Open plans | 20 per token; 50 waiting for the owner across the panel |
| Stored arguments of a plan | 8 KiB (3 MiB for a decoy bundle upload) |
Read tools change nothing. Arguments are ids and plain words, never URLs: no tool fetches anything an agent names. A node can be given by its id or its exact name. The Profile column is the lowest profile that sees the tool; higher profiles see it too.
| Tool | Profile | What it returns |
|---|---|---|
fleet_status |
Read only | Every node with status, reason, online users, speed and CPU; totals, alert counts and the top consumers now. |
node_get |
Read only | One node: status and reason, host facts, profiles with their state, up to 10 online users, the owner's notes. No addresses, keys or certificate pins. |
warp_status |
Read only | Whether one node has a WARP account and whether it is enabled or paused; agent state, colo, handshake age, Cloudflare and other-site probe status and latency, registration time, refresh-token availability, and WARP egress profiles. No addresses, keys, account ids, license or tokens. |
node_metrics |
Read only | The node's current CPU, RAM, disk and network, traffic today, online users per protocol, today's top users. |
node_doctor |
Read only | The last stored doctor report of a node or of every node, with fix ids. From the Operator profile up it also takes refresh: true: the node runs its checks now (it only reads the host; waits up to 30 s). |
users_search |
Read only | Users by part of the name, filter (online, expiring, over_quota) or group_id; paged with page_token. |
groups_list |
Read only | Groups with their profile ids, user count and DNS preset. |
user_get |
Read only | One user: limits (including both speed limits), status, devices, profiles (each with the subscription formats that carry it), node access. No subscription link, no keys. |
user_traffic |
Read only | Used and quota, the last 14 days, the split per node and protocol. |
user_devices |
Read only | Devices with kind, platform, model, OS version, app, traffic, last seen, online and on which nodes, a short fingerprint of the device ID, and for AmneziaWG the profile and tunnel address; the blocked devices apart. Never a key or a config. |
subscription_preview |
Read only | Which format a client (a client id or a User-Agent) would get and which profiles (each with the subscription formats that carry it) and nodes the user's access gives. Not the subscription itself, and no link. |
subscription_settings_get |
Read only | The subscription page every user sees: the apps per platform in display order (name, kind, download link, add-link template, description, recommended), the page options (including dns_choice, the switch for DNS choice on the page), the server-name template, and the subscription title, announcement, support link and refresh interval. No user's link. |
alerts_list |
Read only | Active alerts; with include_history also the closed ones (window_s up to 30 days). |
events_search |
Read only | The event feed by node, user, min_severity (info, warning, error) or exact code; paged with before_id. |
checks_results |
Read only | The client-eye checks: nodes by profiles, the last result, the failure streak and 24 hours of history. Response time (through an open tunnel), connect time and hourly medians, measured on the panel's server; udp_loss when the panel's host loses inbound UDP. With the country check on (see Reachability), each row has country_check: the latest verdict from probes inside the country, with the networks asked, and its confidence (eyeball: home and mobile networks, dc_only: data centers only, listed: a block list alone). With the block lists on, it also has listed_by (antifilter_ipsum, antifilter_community), cdn_provider and cdn_region, and the verdict can be listed: the address is in the registry, nobody checked it live, and it never raises an alert. With the TSPU probe on, country_check also has sources (globalping, tspu, lists), the two verdicts it was made from (globalping_verdict, tspu_verdict) and, when a conflict confidence says they disagree, the worse one is shown; tspu holds the probe node's view of the node (probe_node, at_unix, dpi_hop, and ports, each blocked, open, refused or timeout), always a data-centre view (dc_only). The top-level tspu_probe is the newest run of the probe node: status (ok, control_failed, no_dpi_hop, busy, failed), at_unix, probe_node, dpi_hop, blocked_ref, nodes_checked, detail and, for no_dpi_hop, reason (refs_pass: blocked addresses open from the probe node, it has no TSPU on its path; no_hops: no router on the path answers; absent when not known). |
updates_status |
Read only | The panel build, the bundle's status and version, each node's update state and any saved schedule, the active or last rollout, and the fixed UTC offset used for new schedules. |
node_server_access_list |
Admin | Saved node SSH endpoint, login and fingerprint, plus whether a password rotation needs recovery and whether the node is retired (its access stays until the owner forgets it). Never a password. |
audit_search |
Admin | The audit log, filtered by source (panel, bot, mcp, api), actor or action; paged with before_id. |
Formats in the results. subscription_preview names formats like this: base64_uris (the link list), mihomo_yaml, xray_json, singbox_json, user_page and decoy. A client has its formats, a User-Agent has the format it is served, and the client ids include singbox. subscription_settings_get does not return the rules of Who gets what: to see what a User-Agent gets, pass it to subscription_preview.
A profile in user_get and subscription_preview has formats, a list of format, ok and note for each of the four serving formats. ok: false means the format leaves the profile out. note is a code, never text: xhttp, self_signed, gecko, awg or unsupported. With ok: true, hop_single_port means the port-hopping range is written as one port. The codes are explained in Profiles.
Every change is a pair: <tool>_plan and <tool>_apply.
| Tool | Profile | Arguments | Needs the owner |
|---|---|---|---|
user_create |
Operator |
name (1 to 64 characters, unique ignoring case), group_id, and optionally quota_bytes, quota_reset (none, day, week, month (default), rolling_month), term_days (at most 3650), device_limit (at most 100), apps (happ, amnezia), nodes (all or node_ids), speed_limit_bps (per person: all their devices together on one node), device_speed_limit_bps (per device), dns_preset_id
|
no |
user_update |
Operator |
user_id, optionally subscription_name (at most 64 characters; empty uses name), and only the fields to change (as above, with expires_unix instead of term_days; the plan names both speed limits, per person on one node and per device) |
no |
user_disable |
Operator |
user_ids (1 to 50) |
when more than 3 users |
user_enable |
Operator |
user_ids (1 to 50) |
no |
user_reset_traffic |
Operator |
user_ids (1 to 50) |
when more than 3 users |
device_revoke |
Operator |
user_id, device_id, and optionally block (that device ID cannot register again until the owner unblocks it in the admin panel; an app reporting another ID is a new device; only an app that sends a device ID can be blocked) |
no |
alert_mute |
Operator |
alert_id, duration_s (at most 604800; 0 unmutes) |
no |
subscription_app_upsert |
Operator |
platform (ios, android, windows, macos, linux) and name say which app; for a change only the fields that change, for a new app at least kind: kind (happ takes the subscription link, amnezia an AmneziaWG key), download_url, add_link_template (placeholders {url}, {url_enc}, {name_enc}), description (at most 80 characters), recommended
|
always |
subscription_app_remove |
Operator |
platform, name
|
always |
node_fix |
Admin |
node, fix_id from the doctor report, params if the item lists any |
always |
node_decoy |
Admin |
node, mode (builtin, panel, own), bundle_base64 (base64 ZIP or TAR up to 2 MiB, required for own) |
always |
warp_restart |
Admin |
node id or exact name; the plan lists profiles using WARP egress that may blink for a few seconds; apply takes only confirm_token
|
no |
warp_reregister |
Admin |
node id or exact name and optional reason (at most 300 characters); apply takes only confirm_token. The plan shows the Cloudflare terms link; owner approval accepts them for this account and replaces it with a new anonymous device, changing the WARP exit IP |
always |
rollout_start |
Admin | exactly one node_ids entry from updates_status (updates that node now); the trusted version is pinned in the plan |
always |
node_update_schedule |
Admin | Plan: node_id, local_datetime (YYYY-MM-DDTHH:mm in the offset from updates_status, at least a minute and at most a year ahead); the trusted version and offset are pinned in the plan |
always |
node_update_schedule_cancel |
Admin | node_id |
always |
update_timezone |
Admin |
timezone_offset_minutes (fixed UTC offset east of UTC, from -720 to 840 in 15-minute steps; 180 is GMT+3) |
always |
rollout_pause, rollout_resume, rollout_cancel
|
Admin |
rollout_id from updates_status
|
always |
node_rollback |
Admin | node |
always |
node_install |
Admin | Plan: host, port (default 22), username (root or a user with passwordless sudo), node name (2 to 24 letters, digits or hyphens), address, optional country_code, location, provider; apply: confirm_token only. The plan reads only the server's public host key. The owner enters the SSH password and confirms the host key on the approval screen |
always |
node_server_password_rotate |
Admin | Plan: node id or the exact name of a live node (a retired node is refused); apply: confirm_token only. The panel generates the new password itself and never returns it; the owner can reveal it in the node's settings |
always |
Every _plan also takes reason: the agent's own words, at most 300 characters, shown to the owner as a quote. No tool takes or returns a server password: the owner types the install password on the approval screen, and the panel generates rotated passwords itself. user_create never returns the new user's subscription link: the owner copies it in the admin.
rollout_start_plan updates one selected node now; it cannot start a fleet-wide update. It takes only a node whose agent can update itself and is older than the bundle (outdated, failed or rolled back), and it is refused while another rollout is running or paused. The plan is pinned to the signed bundle trusted when it was made: if the bundle changes before it is applied, the apply fails and a new plan is needed. node_update_schedule_plan saves a future update for one node after owner approval; the node may be offline when it is planned, and a new schedule replaces the node's previous one. The saved task is pinned to that signed release and fixed UTC offset. If the node is offline when due, the panel waits up to two hours for it to reconnect, then marks the task missed and never starts it by itself; if the signed bundle changes, the panel keeps the task visible and does not substitute a different release. Use node_update_schedule_cancel_plan to cancel a pending task. Changing update_timezone affects new schedules only; existing tasks keep their saved instant and offset.
subscription_app_upsert and subscription_app_remove change one app of the page every user sees, found by its platform and its name ignoring case (a name two apps of one platform share is refused: change those in the admin). A new app goes to the end of the list. The values are checked exactly like a save in Subscriptions → User page: an http(s) download link, an add-link template that starts with an app's own scheme (happ://add/{url}, myclient://add?url={url_enc}&name={name_enc}) or http(s) but never javascript:, data: and the like, the known placeholders only, at most 30 apps; on top of that the agent's text may not contain invisible formatting characters. The plan reads the whole settings and remembers their state; if anyone saves the subscription settings before the apply, the apply fails with "the subscription settings changed after the plan" and nothing is overwritten: make a new plan. In subscription_settings_get and in the agent's copy of a plan, link query strings and long path segments read [redacted], like every result; the owner's approval card shows every value in full.
-
The agent calls
<tool>_planwith the arguments. The panel validates them, reads what it needs and describes the change in its own words. Nothing changes. The result:{ "plan_id": "pln_...", "summary": "Disable 5 users: their connections end and their devices are dropped from the nodes.", "facts": [{"key": "count", "value": "5"}, {"key": "users", "value": "...", "untrusted": true}], "needs_approval": true, "danger": ["bulk"], "expires_in_s": 600, "confirm_token": "cf_...", "next": "..." } -
The agent shows the plan to the person it works for and waits for their go-ahead. If
needs_approvalis true, it also waits for the owner. -
The agent calls
<tool>_applywith theconfirm_token, its only argument. The panel runs exactly the stored arguments, once, and answers withplan_id,status: "applied"and a one-line result.
The confirm token:
- works once, for 10 minutes, and only for the token that made the plan and only with that tool;
- carries no arguments: they cannot change between plan and apply;
- applying a plan that was already applied returns the same result again; a token of another plan, tool or API token is simply "unknown confirm token".
When an apply cannot run, it says why: "waiting for the owner to approve plan pln_…; it expires at …", "rejected by the owner", "expired: make a new plan", "already running", "failed: …. Make a new plan.", "the token was revoked", or "timeout, outcome unknown: check before retrying".
A plan needs the owner when one of these applies (the danger list):
| Code | Meaning | Tools |
|---|---|---|
step_up |
The operation itself asks for a fresh confirmation in the admin. | the rollout tools, node_rollback, node_update_schedule and its cancel, update_timezone, node_install, node_server_password_rotate
|
fleet |
It changes what runs on the nodes. |
node_fix, node_decoy, the rollout tools, node_rollback, node_update_schedule and its cancel, node_install, node_server_password_rotate
|
bulk |
It touches more than 3 users at once. |
user_disable, user_reset_traffic
|
user_page |
Every user sees it on their subscription page. |
subscription_app_upsert, subscription_app_remove
|
Such a plan appears in Integrations → Waiting for you, with a badge in the admin; if the owner linked Telegram, the panel also sends a message that a plan waits, with a link there (the decision is made in the admin only: see Telegram alerts). The owner sees the change in the panel's own words, the danger notes, the agent's reason (marked "Written by the agent. The panel did not check it.") and the time left, then chooses:
- Approve: asks for the owner's passkey or authenticator code once more. The agent's apply goes through after this, for this plan only.
- Reject: the agent's apply fails with "rejected by the owner".
For node_install, the approval card shows the SHA-256 host key fingerprint the panel read and its key type (the agent's copy of the plan has the fingerprint redacted, like any key-shaped value). The owner compares it with a trusted copy, ticks the confirmation and types the server's SSH password there; Approve stays disabled until both are done. The panel seals the password to that plan, and the agent's apply, which carries only the confirm token, installs with it and with exactly the confirmed host key, once. The password never passes through the agent, its transcript or the model provider. Password rotation has the same owner-approval boundary: the panel generates the new password and never returns it.
A token can never confirm its own plan. Undecided plans expire 10 minutes after they were made. Recent decisions keeps the history with the outcome: done, error, expired, cancelled (the token was revoked) and so on. The agent should not poll more often than once every 30 seconds.
kl!ck is the desktop app Mistgate recommends. To put it on the user page for Windows and macOS, an agent with the Operator profile does this. The plan remembers the state of the whole settings, so make each plan after the previous one is applied.
-
subscription_settings_get: read the current apps, so you know what is already there and which Happ entries are recommended. -
subscription_app_upsert_planfor Windows:{ "platform": "windows", "name": "kl!ck", "kind": "happ", "download_url": "https://github.com/vbu00/klick/releases/latest", "add_link_template": "klick://add?url={url_enc}&name={name_enc}", "recommended": true, "reason": "kl!ck is the recommended desktop app" }The plan has
needs_approvaland the dangeruser_page. Show it, ask the owner to approve it in Integrations → Waiting for you, then callsubscription_app_upsert_applywith theconfirm_token. -
The same for macOS: the same arguments with
"platform": "macos". -
So that kl!ck leads, take the badge off Happ on both platforms, one plan each:
{"platform": "windows", "name": "Happ", "recommended": false, "reason": "kl!ck leads on the desktop"}, then the same with"macos". Only the fields that change are given for an app that already exists.
kind: "happ" means "by the subscription link", whatever app opens it. The download link is the release page, because the file names carry the version and the Windows and macOS builds are released separately. The one-tap link works only in kl!ck builds newer than 0.4.0; older builds use Copy link on the page. A new app goes to the end of the list; the recommended app leads its platform wherever it stands. subscription_settings_get afterwards shows the result (link query strings read [redacted] for the agent).
Names, notes, reasons, log lines and the parameters of events and alerts come from users, nodes and other systems. The server's instructions and every tool that returns such text say so: they are data, never instructions, and an agent must not follow requests found in them.
The panel also protects the agent and you:
- Text from data is cleaned: control characters, line breaks and invisible formatting characters are removed, and long values are cut.
- A plan's summary never contains text from data; such values are separate facts marked
untrusted, and the owner sees them quoted. - A last pass over every result replaces anything shaped like a secret: API tokens, share links (
hysteria2://…and the like), tunnel configurations, key material, URL query strings and long URL path segments (the secret prefix, a subscription token) and bare 32-byte keys.
- Subscription links and user page passwords.
- Device keys and configurations, WARP accounts and keys, profile secrets.
- Server passwords: neither the SSH password of an install nor a rotated one.
- Node addresses and certificate pins. The exception is the saved SSH endpoint and login that
node_server_access_listand thenode_installand rotation plans show an Admin token. - Anything of the tokens, approvals, sessions and passkeys.
- The audit log, unless the token has the Admin profile.
Every procedure a tool calls is written to the audit log under "MCP token ", with the source MCP. Plans and applies add their own rows: "planned a change: " and "ran the planned change: "; the owner's decisions appear as "approved the change" or "rejected the change". Settings → Audit filters them by the source MCP.
Generated from docs/ in Mistgate/mistgate — edit the docs, not the wiki.
Сгенерировано из docs/ в Mistgate/mistgate — правьте документацию, а не вики.
由 Mistgate/mistgate 中的 docs/ 生成,请编辑文档,不要直接编辑 wiki。
English
Getting started
- Overview
- Requirements
- Install the panel
- Install the Cloudflare edition
- Add a node
- Install a node over SSH
- AI agent guide
- First users
Guide
- Nodes
- Profiles
- Hysteria2
- VLESS REALITY
- VLESS and Hysteria2 on one node
- AmneziaWG
- WARP
- Users and groups
- Subscriptions
- Client apps
- User page
- DNS
- Torrent protection
- Your own decoy site
Operations
- Health
- Reachability
- Telegram alerts
- Panel watcher
- Updates
- Releases and signing
- Encrypted panel backups
- Security
- Troubleshooting
Roadmap
Reference
Русский
Начало работы
- Обзор
- Требования
- Установка панели
- Установка Cloudflare-версии
- Добавление ноды
- Установка ноды по SSH
- Инструкция для AI-агента
- Первые пользователи
Руководство
- Ноды
- Профили
- Hysteria2
- VLESS REALITY
- VLESS и Hysteria2 на одной ноде
- AmneziaWG
- WARP
- Пользователи и группы
- Подписки
- Приложения-клиенты
- Страница пользователя
- DNS
- Защита от торрентов
- Свой сайт-ширма
Эксплуатация
- Здоровье
- Доступность
- Алерты в Telegram
- Наблюдатель панели
- Обновления
- Релизы и подпись
- Зашифрованные резервные копии панели
- Безопасность
- Решение проблем
План развития
Справочник