Skip to content

FAQ & troubleshooting ​

Every call returns HTTP 401 ​

Two causes, in order of likelihood:

  1. 2FA is enabled on the account. The wg-easy API speaks Basic Authentication only and rejects TOTP-protected accounts. The error message says so explicitly. Disable TOTP for that account or use another one.
  2. The username or password is wrong. They are the web UI login, not an API token — wg-easy has no separate API credentials.

The server starts but every tool call fails with "missing required environment variable(s)" ​

The server started without credentials, which it does on purpose so registries can enumerate its tools. Your MCP client is not passing the env block through. Check the config for the client you are using in Connecting clients, and remember that claude mcp add needs each variable as its own -e flag before the --.

get_server_info reports an error for the information section ​

/api/information fetches the latest wg-easy release from GitHub, and it returns HTTP 500 when the container has no outbound internet access. The tool collects its three sections independently, so general and interface still come back normally. If you do not need release-update status, this is cosmetic; otherwise, give the wg-easy container egress.

Can I make it read-only? ​

Yes, since 0.4.0: WG_EASY_READ_ONLY=true registers list_clients, get_client and get_server_info, and nothing else. wg-easy itself still has no read-only account, so this is enforced by the server, not by the credentials — anything that can read the process environment still holds VPN admin.

Note that get_client_config and get_client_qrcode are not in that set, although both are reads. What they read is a client's private key in the clear, and a read-only mode that leaves key disclosure standing is not the mode its name promises. They are out of WG_EASY_ALLOW_TOOLS=essential for the same reason; name them where a session should also hand out configurations.

Beyond the switch:

  • use an MCP client that asks before running non-read-only tools — the tools carry readOnlyHint, destructiveHint and idempotentHint annotations for exactly this,
  • rely on the five tools that ask a person — create_client, update_client, enable_client, delete_client and generate_one_time_link — which no single call can bypass.

Why did update_client not change the field I asked for? ​

update_client only accepts the fields in its schema. Anything else — including wg-easy's preUp/postUp/preDown/postDown shell hooks, which run as root on the server — is dropped before the request is built, deliberately and with a test covering it. If you need a hook changed, do it in the wg-easy UI.

Note also that the wg-easy update endpoint requires the complete client object, so the tool reads the current state and merges your changes into it. A field you did not mention keeps its current value.

The tool output starts with "[untrusted data]" ​

That is the marker described in Security. It tells the model that client names and similar free-form fields are data, not instructions. The actual payload follows after a blank line.

The answer carries a truncated field ​

An upstream payload exceeded the 60 000-character budget, so the longest strings were shortened (… (N more characters omitted)) or entries were dropped from the longest list. truncated.fields names every path that was cut with what survived and what was there, and truncated.note names the call that fetches the remainder — usually narrowing list_clients with filter, or switching to get_client for one specific client.

The output says the login is "repeated from memory" ​

The instance refused the credentials, and that answer is repeated for ten seconds instead of being tried again. Every tool call carries the admin credentials, so every call is a login attempt, and a model that retries a 401 turns one wrong password into a stream of failed logins from the host you administer the VPN from. The message says when the next real attempt is possible. Fix WG_EASY_USERNAME/WG_EASY_PASSWORD and restart the server; note that the wg-easy API does not work at all while 2FA is enabled for the account.

The output says the instance answered past a ceiling ​

A successful response body larger than 8 MiB is refused rather than read: the other end of the connection is not always wg-easy — a typo in WG_EASY_URL reaches whoever owns that name, and WG_EASY_INSECURE_TLS trusts whatever answers — and a body that never ends would otherwise be a server that never answers again. Narrow the request, or check that WG_EASY_URL points where you think it does.

Does it work with wg-easy v14 or older? ​

No. It targets the v15 REST API. Older versions expose a different, session-based API that this server does not implement.

A wg-easy upgrade broke a tool ​

Likely. The wg-easy API is not declared stable and its shapes change between releases. Please open an issue with the wg-easy version and the failing tool — redact keys, hostnames and IPs first.

Where do I ask something not covered here? ​

GitHub Discussions for questions and ideas; Issues for reproducible problems; and private reporting for anything security-related.

One tool I expected is missing ​

Something narrowed the list. In order of likelihood:

  • WG_EASY_READ_ONLY is set, and it is a write tool.
  • WG_EASY_ALLOW_TOOLS is set and does not name it — it is an allow list, so anything not named is out.
  • WG_EASY_DENY_TOOLS names it, possibly through a prefix such as list_*.

A filtered tool is not registered at all, so it is missing from tools/list and answers tools/call with "tool not found". There is no state where it is hidden but still callable.

What it is not is a typo in one of those variables: an entry that matches no tool stops the server at startup and says which entry it was. See choosing the tools that load.

Released under the MIT License.