remnawave-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@remnawave-mcplist all users with their subscription status and traffic usage"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
remnawave-mcp
An MCP server for the Remnawave panel API.
Published under the
@folexzscope: the unscopedremnawave-mcpname on npm belongs to an unrelated project that targets Remnawave 2.7.4 and does not work with 2.8.0+.
npx -y @folexz/remnawave-mcp # configured via REMNAWAVE_BASE_URL + REMNAWAVE_API_TOKEN_READ/_WRITEIt covers all 205 operations across 28 controllers of Remnawave API v3.3.2 — users,
nodes, hosts, config profiles, squads, subscriptions, node plugins, infra billing, system
stats — generated from the panel's own OpenAPI document rather than hand-written. Point a
newer spec at npm run build-spec and the tool surface follows.
Highlights
Spec-driven, and self-updating.
npm run update-specfetches the newest OpenAPI document from Remnawave's own published copy and rebuilds the catalogue; every tool's input schema comes straight from the operation's parameters and request body. Nothing about the API is written by hand, and the rebuild prints a diff naming every operation added, removed or renamed, so a version bump cannot silently drop a tool.Bounded context cost. 205 typed tools would cost ~39k tokens of
tools/liston every request. The default profile exposes 5 tools (~1.4k tokens) and still reaches every operation — see Why not 205 tools.Two-token least-privilege auth. A read token and an optional write token.
GETuses the read token;POST/PATCH/PUT/DELETEuse the write token. Without a write token, mutating tools are not registered at all — the server is physically read-only.Guard rails for a live panel. Mutations are serialised with a minimum interval and retried with backoff, because every config write makes the panel push config to all nodes and restart Xray. Bulk and delete operations additionally require
confirm: true.Field notes baked in. The gotchas below are attached to the operations they affect, so they appear in the tool description and in
remnawave_describe_operationoutput.No requests that cannot succeed. 16 endpoints (auth, passkeys, API-token management) are served only to a logged-in admin JWT and reject API tokens. They are detected from the spec and refused locally with an explanation instead of being sent.
Escape hatches.
remnawave_request_read/remnawave_request_writereach any path, including undocumented routes and query syntax OpenAPI cannot express.
Requirements
Node.js ≥ 18
A Remnawave panel (3.x) reachable over HTTPS
An API token from the panel: Settings → API tokens. Remnawave 3.x supports scoped tokens — mint one with read scopes and, if you want mutations, a second with write scopes.
Install
The quick path is npx — see Register with Claude Code. To run
from source:
git clone https://github.com/folexz/remnawave-mcp.git
cd remnawave-mcp
npm install
npm run buildConfiguration
All configuration is environment variables supplied by your MCP host. No files are read.
Variable | Required | Default | Description |
| yes | — | Panel origin, e.g. |
| yes | — | Read token. Alias: |
| no | — | Write token. Omit to run read-only. |
| no |
|
|
| no | — | Comma-separated controller slugs; overrides the profile's typed-tool selection. |
| no |
| Input schemas larger than this are collapsed in |
| no |
| Minimum gap between two mutations. |
| no |
| Retries on transport failures, 429 and 5xx. |
| no |
| Per-request timeout. |
| no |
|
|
| no |
|
|
Register with Claude Code
Read-only (recommended default):
claude mcp add remnawave --scope user \
--env REMNAWAVE_BASE_URL=https://panel.example.com \
--env REMNAWAVE_API_TOKEN_READ=your_read_token \
-- npx -y @folexz/remnawave-mcp@latestWith mutations enabled and typed tools for the everyday controllers:
claude mcp add remnawave --scope user \
--env REMNAWAVE_BASE_URL=https://panel.example.com \
--env REMNAWAVE_API_TOKEN_READ=your_read_token \
--env REMNAWAVE_API_TOKEN_WRITE=your_write_token \
--env REMNAWAVE_TOOL_PROFILE=core \
-- npx -y @folexz/remnawave-mcp@latest@latest makes npx resolve the newest published version on each launch. To run a local build,
replace the command with node /absolute/path/to/remnawave-mcp/dist/index.js.
Register with Claude Desktop / other MCP clients
{
"mcpServers": {
"remnawave": {
"command": "npx",
"args": ["-y", "@folexz/remnawave-mcp@latest"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN_READ": "your_read_token"
}
}
}
}Why not 205 tools
tools/list is re-sent to the model on every request, so its serialized size is a permanent
context tax. Measured on this spec (npx tsx scripts/tool-stats.ts):
Profile | Tools (read+write) |
| ≈ tokens | Tools (read-only) | ≈ tokens |
| 5 | 5.6 KB | ~1.4k | 4 | ~1.2k |
| 91 | 71 KB | ~17.8k | 38 | ~5.9k |
| 210 | 156 KB | ~39k | 92 | ~13.9k |
Remnawave's DTOs are the reason full is so expensive: one dereferenced host object is ~30 KB
of JSON Schema on its own, because it embeds every inbound and security variant.
So the server does not choose between "one tool per operation" and "one blunt dispatcher" — it ships both, and lets the profile decide how much is advertised:
Catalogue tools (always on, 3 tools).
remnawave_list_operationsbrowses and searches the catalogue and returns one compact line per operation;remnawave_describe_operationreturns the complete JSON Schema plus field notes for one operation;remnawave_callexecutes any of the 205 by name. The usual loop is list → describe → call, and it costs the same 1.4k tokens no matter how big the API gets. This is the same lazy-loading idea an agent harness uses when it defers tool schemas.Typed tools (profile-selected). One generated tool per operation for the controllers you actually work with —
corecovers users, nodes, hosts, config profiles, internal squads, system and the two bulk-action controllers;fullcovers everything;minimalnone. Schemas overREMNAWAVE_MAX_SCHEMA_BYTESkeep their top-level fields and drop the nesting, with a pointer toremnawave_describe_operationfor the full version.Escape hatches (2 tools). Raw GET and raw write for anything the spec misses.
Every route goes through the same executor, so the write gate, the destructive-confirm gate, path templating and query handling behave identically whichever surface you use.
Pick a profile by taste: minimal if you have many MCP servers connected, core if you want
the everyday operations one call away, full if context is not a concern.
Field notes — behaviour the spec does not document
All of these were hit against a live 3.3.2 panel and are attached to the affected operations in the tool descriptions.
PATCH /api/config-profilesis a replace, not a patch. The body is{uuid, config}andconfigmust be the complete, valid Xray config. A fragment fails withA061: Config doesn't have inbounds. Correct sequence:GET /api/config-profiles/{uuid}→ edit the returnedconfigobject in place →PATCHthe whole thing back.The panel does not answer on
127.0.0.1:3000, even from the panel host itself and even thoughdocker-proxyis listening there (curlreturns exit 52, empty reply). Always use the public HTTPS origin with a Bearer token.Every response is wrapped in
{"response": ...}. This server unwraps it, so tool output is the payload itself.A host binds to a profile through the nested
inbound.configProfileUuid(plusinbound.configProfileInboundUuid), not a top-levelconfigProfileUuid. Verified against live hosts: nested present, top level absent.A run of
PATCHes will take the panel down. Each config write pushes to every node and restarts Xray there; several in a row and the panel's own TLS listener stops answering. The client serialises mutations (REMNAWAVE_WRITE_MIN_INTERVAL_MS, default 1500 ms) and retries transport failures with exponential backoff and jitter. Do not defeat it by firing bulk updates in parallel.POST /api/subscription-templatescreates an empty template only. The content is uploaded by a separatePATCH /api/subscription-templates. JSON and YAML bodies cannot be updated in the same call.serverDescriptionon a host is capped at 30 characters (confirmed bymaxLengthin the spec). It is also what makes a Hysteria2 host render properly in Happ instead of raw JSON.Sixteen endpoints are admin-JWT only — the whole
authandpasskeyscontrollers plus API-token management (GET/POST /api/tokens,DELETE /api/tokens/{uuid},GET /api/tokens/scopes). The panel answers an API token with 401/403 there. This server detects them from the spec and refuses locally;REMNAWAVE_ALLOW_ADMIN_JWT_OPS=1lifts the gate if the token you configured really is an admin JWT.GET /api/users/streamanswers with newline-delimited JSON, not one document. It is parsed into an array of user records rather than handed back as a blob of text.PATCH /api/hostsis a real partial patch —{uuid, serverDescription}alone works. Only config profiles have the replace-the-whole-thing semantics. Verified live.Errors come back as
{message, errorCode}; theerrorCode(e.g.A061) is included in this server's error text.
Tool coverage
Every controller is reachable through remnawave_call and the escape hatches. The
typed column shows which get individual tools under REMNAWAVE_TOOL_PROFILE=core.
Controller slug | Operations | Typed under |
| 17 | yes |
| 18 | — |
| 15 | yes |
| 12 | — |
| 12 | yes |
| 12 | yes |
| 10 | yes |
| 9 | yes |
| 8 | — |
| 7 | — |
| 7 | — |
| 7 | — |
| 7 | yes |
| 7 | — |
| 7 | — |
| 7 | — |
| 6 | — |
| 5 | — |
| 5 | — |
| 5 | — |
| 4 | — |
| 4 | yes |
| 4 | — |
| 3 | — |
| 2 | — |
| 2 | — |
| 2 | — |
| 1 | — |
Total | 205 |
Run remnawave_list_operations against a live server for the exact, current set.
Examples
Browse and call without any typed tools:
// 1. What is there?
{ "tool": "remnawave_list_operations", "arguments": { "controller": "nodes" } }
// 2. What does it take?
{ "tool": "remnawave_describe_operation",
"arguments": { "operation": "remnawave_post_nodes_uuid_actions_restart" } }
// 3. Do it.
{ "tool": "remnawave_call",
"arguments": { "operation": "remnawave_post_nodes_uuid_actions_restart",
"params": { "uuid": "…" } } }Editing a config profile safely (the A061 trap):
// Read the whole profile first — PATCH replaces the config wholesale.
{ "tool": "remnawave_call",
"arguments": { "operation": "remnawave_get_config_profiles_uuid", "params": { "uuid": "…" } } }
// Send the full, edited config back.
{ "tool": "remnawave_call",
"arguments": { "operation": "remnawave_patch_config_profiles",
"params": { "body": { "uuid": "…", "config": { /* complete Xray config */ } } } } }Query syntax the spec cannot express:
{ "tool": "remnawave_request_read",
"arguments": { "path": "/api/users",
"query": { "size": 25, "start": 0,
"filters[0][id]": "status", "filters[0][value]": "ACTIVE" } } }Testing
npm run build
npm test # 50 unit tests + the offline smoke suite
npm run test:unit # unit tests aloneThe unit tests cover the parts that fail quietly: $ref expansion through Remnawave's
recursive DTOs, tool-name derivation (length budget, determinism, collision detection), the
catalogue diff, both write gates, the admin-JWT gate, schema collapsing and NDJSON parsing.
Read-only checks against a real panel
With a panel reachable and a read token in the environment, the smoke script also runs live read-only calls (never a mutation):
REMNAWAVE_BASE_URL=https://panel.example.com \
REMNAWAVE_API_TOKEN_READ="$REMNAWAVE_API_TOKEN" \
npm run smokeRun it where the token already lives (e.g. on the panel host) so the secret never travels. The script prints shapes — types, key names, array lengths — and never payload values, so its output is safe to paste into an issue.
Guard-rail checks deliberately point at http://127.0.0.1:9, so a gate that ever failed open
could not reach a real panel.
Verifying the write path
Reads cannot prove that token routing, the throttle, the confirm gate and partial-patch
semantics actually work. scripts/write-check.mjs proves them on objects nobody is attached
to, and puts back the one pre-existing object it touches:
REMNAWAVE_BASE_URL=https://panel.example.com \
REMNAWAVE_API_TOKEN_READ="$T" REMNAWAVE_API_TOKEN_WRITE="$T" \
node scripts/write-check.mjs --i-understand-this-mutates [--host-uuid <uuid>]It creates an internal squad with no inbounds and no members and deletes it again, then
rewrites serverDescription on one host and restores the original value. It refuses to start
without the acknowledgement flag, and reports a non-zero exit if anything is left behind.
Against a live 3.3.2 panel it confirmed: the confirm gate holds on a real DELETE; a partial
PATCH /api/hosts works; the panel rejects a 31-character serverDescription; the original
value (including null) round-trips; and consecutive mutations were spaced 1525 and 1524 ms
apart against a configured 1500 ms floor.
Inspect locally
REMNAWAVE_BASE_URL=https://panel.example.com REMNAWAVE_API_TOKEN_READ=xxx npm run inspectUpdating the API spec
Everything about the API comes from one file, so tracking a new Remnawave release is one command:
npm run update-spec # fetch the newest spec + rebuild the catalogue
npm run update-spec -- --strict # additionally fail if any operation disappeared or was renamed
npm run build && npm test # compile and verifyWhere the spec comes from
https://cdn.remna.st/docs/openapi.json — published by Remnawave's own
Build&Push OpenAPI Specs workflow on every upstream tag, so it always describes the newest
release. Override with --url <u> or REMNAWAVE_SPEC_URL to pin a different source.
A panel instance is not a usable source: docs are disabled unless the deployment turns them
on, and even then Swagger is mounted at /backend-tools/swagger, which the usual reverse proxy
does not route. Probing a live 3.3.2 panel returned 404 on every conventional spec path.
The download is only written to disk after it parses as an OpenAPI document with a non-empty
paths, so an error page or a captive portal cannot clobber a working spec.
What to check afterwards
build-spec diffs the new catalogue against the previous one and prints every change:
build-spec: Remnawave API v3.4.0 -> 211 operations, 28 controllers, 315 KB
methods: DELETE=22 GET=90 PATCH=19 POST=78 PUT=2 admin-JWT-only: 16
diff: API version 3.3.2 -> 3.4.0
REMOVED — tools that will disappear (1):
remnawave_get_old_thing (GET /api/old-thing)
added (7):
...REMOVED / RENAMED are breaking for anyone whose prompts or scripts name those tools.
--strictturns them into a non-zero exit, which is the flag automation should use.added is safe; the new operations are reachable through
remnawave_callimmediately and get typed tools if their controller is in the active profile.schema changed is worth a glance for the operations you actually use.
npm test then re-checks that the on-disk catalogue matches a fresh build, that all tool names
are unique and inside the 64-character budget, and that the guard rails still hold.
Automating it
npm run update-spec -- --strict # exits non-zero on a breaking catalogue change
npm test
npm version minor --no-git-tag-version
git commit -am "chore: Remnawave API 3.4.0" && git push
git tag "v$(node -p "require('./package.json').version")" && git push --tagsThe pushed tag triggers the release workflow, which republishes to npm. Clients registered with
@folexz/remnawave-mcp@latest pick the new version up on their next launch.
Releasing (maintainers)
First publish — necessarily manual
npm cannot configure a trusted publisher for a package that does not exist yet: the setting lives on the package's own settings page. That is a known, still-open limitation (npm/cli#8544), and it applies to scoped packages too. So version 0.1.0 has to go up from a logged-in machine:
npm whoami # must print the account that owns the @folexz scope
npm publish --access public--access public is required: scoped packages default to restricted.
Then switch to tokenless releases
Once the package exists, on npmjs.com → @folexz/remnawave-mcp → Settings → Trusted Publisher,
add a GitHub Actions publisher with repository folexz/remnawave-mcp and workflow
release.yml. repository.url in package.json must match the GitHub repository exactly — it
does.
After that, .github/workflows/release.yml publishes on any pushed vX.Y.Z tag via OIDC — no
token, no secret, with provenance attached automatically:
npm version patch --no-git-tag-version
git commit -am "chore: v0.1.1"
git push
git tag v0.1.1 && git push origin v0.1.1The workflow reinstalls from the lockfile, rebuilds, runs the unit tests and the offline smoke
suite, and fails fast if the tag does not match package.json.
Clients registered with @folexz/remnawave-mcp@latest pick the new version up on their next
launch.
Security notes
Tokens are read from the environment only and are never logged. Logs go to stderr; stdout is the MCP JSON-RPC channel.
Prefer configuring only
REMNAWAVE_API_TOKEN_READ. Mutating tools do not exist without a write token, so a compromised or confused client cannot change the panel.Subscription endpoints return working client configs. Treat their output as secret.
Never commit real tokens.
.envis gitignored;.env.exampleshows the shape.
Known limitations
Body validation is delegated to the panel. This server checks only that required arguments and a required
bodyare present; it does not validate the body's inner shape against the schema. That is deliberate — the panel already validates every field and answers with a precisemessage+errorCode(e.g.A061), and duplicating that locally would mean shipping a JSON Schema validator plus a second, inevitably drifting, copy of the rules. The cost is that a malformed body costs one round trip to find out.The escape hatches bypass the per-operation gates.
remnawave_request_writeis raw by design: it still requires a write token and still goes through the throttle and the retry logic, but it does not apply the destructiveconfirmgate or the admin-JWT check, because it has no operation to look those up from. Preferremnawave_callunless you need a route the spec does not describe.Tools are only as current as the shipped spec (v3.3.2). A panel on a different minor version may expose routes it does not describe; that is what the escape hatches are for. See Updating the API spec.
Admin-JWT endpoints are gated, not implemented. This server carries API tokens; it does not perform an admin login, hold a session, or refresh a JWT. If you supply an admin JWT as the token and set
REMNAWAVE_ALLOW_ADMIN_JWT_OPS=1, those 16 endpoints become callable, but expiry and renewal are your problem.The Prometheus basic-auth metrics endpoint is not part of this spec and is not exposed.
write-check.mjsmutates. It is a maintainer tool, excluded fromnpm test, and refuses to run without an explicit acknowledgement flag.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
34 production API tools over one hosted MCP endpoint.
Official Sevalla MCP — full PaaS API access through just 2 tools.
MCP Server for agents to onboard, pay, and provision services autonomously with InFlow
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/folexz/remnawave-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server