npm-mcp
Provides tools for managing an Nginx Proxy Manager instance, including reverse-proxy routing (proxy, redirection, stream, and 404 hosts), TLS/SSL certificates, access lists, users and 2FA, and instance settings.
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., "@npm-mcpList all proxy hosts and show their statuses."
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.
npm-mcp
Model Context Protocol server for Nginx Proxy Manager
Manage reverse-proxy routing, TLS certificates, access lists, and stream forwards conversationally — with guardrails that assume you will eventually point it at production.
Contents
Related MCP server: npm-mcp
Why this exists
Nginx Proxy Manager has a full REST API and no MCP server. This is that server — but the interesting part isn't the plumbing, it's the constraints.
A reverse proxy is a single point of failure for everything behind it. An agent with write access to one can take down services it was never asked to touch. So the design starts from that:
Tools are generated from the API's own OpenAPI document, not hand-written. The document is pinned in-tree, and a drift test fails CI if the upstream surface changes — instead of tools silently 404-ing at runtime.
Every result crosses one redaction boundary that fails closed. It raises on anything it can't inspect rather than passing it through.
Guardrails are mutation-tested. Every safety control has a test proven to go red when the control is disabled.
How it works
flowchart LR
C["MCP Client"] -->|"Bearer (optional)"| S
subgraph S["npm-mcp"]
direction TB
A["Bearer verifier<br/><i>hmac.compare_digest</i>"] --> G["Guardrails<br/><i>S1 · S2 · S6 · S7 · S8</i>"]
G --> T["66 generated tools"]
T --> R["serialize_result()<br/><i>redact + cap</i>"]
end
S -->|"JWT, auto-refreshed"| N["Nginx Proxy Manager"]
P["npm-openapi.json<br/><i>pinned, in-package</i>"] -.->|generates| TTool signatures are built from the pinned document at import time, so
create_proxy_host exposes 18 typed arguments with real enums — not an
opaque **kwargs passthrough.
Quick start
uv sync
cp .env.example .env # then fill in NPM_URL / NPM_IDENTITY / NPM_SECRET
uv run npm-mcp{
"mcpServers": {
"npm": {
"command": "uv",
"args": ["run", "npm-mcp"],
"env": {
"NPM_URL": "https://nginx-proxy-manager.example.net",
"NPM_IDENTITY": "npm-mcp@example.net",
"NPM_SECRET": "…",
"NPM_MCP_TRANSPORT": "stdio"
}
}
}
}{
"mcpServers": {
"npm": {
"type": "http",
"url": "https://npm-mcp.example.net/mcp",
"headers": { "Authorization": "Bearer <NPM_MCP_BEARER_TOKEN>" }
}
}
}FastMCP serves at /mcp. A trailing slash 307-redirects, which some clients
mishandle — don't let a proxy rewrite the path.
Callget_guidance first. It reports response shapes, the
disable-vs-delete distinction, which latches are currently open, and the
active protected-domain list.
Authentication
Two layers, easy to conflate:
Direction | Mechanism | |
Inbound | client → npm-mcp | Optional |
Outbound | npm-mcp → NPM | Account credentials → short-lived JWT, refreshed automatically. Callers never see or supply it. |
NPM issues no long-lived API keys, which is why the server holds credentials rather than accepting a token.
POST /tokens has two possible responses: a token, or a 2FA challenge.
If the account has 2FA enabled, set NPM_TOTP_SECRET — otherwise the server
fails at startup, naming both remedies, rather than coming up healthy and
breaking on the first tool call.
Tool catalog
66 tools = 65 API operations + get_guidance.
Family | # | Representative tools |
🔀 Proxy hosts | 7 |
|
↪️ Redirection hosts | 7 |
|
🚫 404 hosts | 7 |
|
🔌 Streams | 7 |
|
🔐 Access lists | 5 |
|
📜 Certificates | 10 |
|
👤 Users | 8 |
|
🔑 User 2FA | 5 |
|
⚙️ Settings | 3 |
|
📋 Audit log | 2 |
|
ℹ️ Meta | 4 |
|
🧭 Guidance | 1 |
|
Names derive from the OpenAPI operationId, so list operations are get_*,
not list_*.
Three operations are deliberately not exposed: requestToken,
refreshToken, loginWith2FA. They're the server's own auth plumbing, and
requestToken accepts an arbitrary identity and secret — registering it
would turn this server into a credential-testing oracle against NPM, with
every attempt attributed to the service account.
No pagination exists. Not one endpoint accepts
limit/offset. Tools accept them and slice client-side; the tool descriptions say so.expandis a per-endpoint enum, not a passthrough — proxy-hosts takesaccess_list,owner,certificate; certificates take onlyowner. Out-of-enum values are rejected before the request is sent.
Safety model
Writes are enabled by default. This server can rewrite the routing table
for every service behind the proxy. Set NPM_READ_ONLY=1 to disable all
mutations.
Control | Override | |
| Rejects every mutating tool, checked before any guardrail read | — |
S1 | Refuses |
|
S2 | Every | per-call |
S5 | Every mutation emits one audit line; NPM's own audit log is queryable | — |
S6 | Every mutating operation under |
|
S7 | Refuses to modify, disable, delete, or | none |
S8 |
|
|
S1 matches ANY protected domain, not ALL. ALL would let the guardrail be disarmed through the tools it guards: add one unrelated domain to a host and protection evaporates.
S1 covers
update, not just delete/disable. Otherwise you strip the protected name out ofdomain_names, then delete cleanly — same outage.S1 matches on current upstream state, never the submitted body. Checking the request would let the strip-then-update path walk straight through.
S1 wildcards match in both directions.
NPM_PROTECTED_DOMAINS=*.example.netmust protectapp.example.net. It once matched nothing and suppressed the "unprotected" warning, because the value was explicitly set.S2 scopes by HTTP method, not name prefix. A
delete_*rule missesdisable_user_2fa— aDELETEthat strips someone's second factor.S6 is a rule, not a list. An enumerated version silently omitted
update_user, so the latch stayed shut whileis_disabled: truelocked out an admin.S7 has no override. A server that can delete its own credentials locks itself out permanently.
Configuration
Variable | Meaning |
| Base URL of the NPM instance |
| Account email |
| Account password |
Variable | Default | Meaning |
| unset | Inbound token. Unset ⇒ no inbound auth |
|
|
|
|
| Bind address |
|
| Bind port |
Variable | Default | Lifts |
|
| — ( |
| derived from | S1 denylist, comma-separated |
|
| S1 |
|
| S6 |
|
| S8 |
Variable | Default | Meaning |
| unset | Base32 seed; only if the account has 2FA |
|
| Verify NPM's certificate |
|
| Upstream timeout, seconds |
|
| Response cap before truncation |
|
| Hint toward |
|
|
Deployment
docker build -t npm-mcp:latest .
docker compose up -dThe container joins an existing Docker network alongside NPM and publishes no ports. NPM reaches it by container DNS and terminates TLS, so the Bearer token never crosses the wire in plaintext.
No
build:key in the compose file. A compose-string deploy (Portainer, for one) ships no build context, so the image is built first and referenced by tag.The healthcheck resolves the bind host instead of hardcoding
127.0.0.1. With a customNPM_MCP_HTTP_HOSTthe naive version marks a perfectly healthy container as unhealthy forever. It also short-circuits understdio, where nothing is listening at all.The authenticating warm-up runs in the server lifespan, so a misconfiguration fails the healthcheck rather than coming up green and breaking on first use.
Testing
uv run pytest # 420 tests
uv run ruff check
uv run ruff format --checkRoughly 4,700 lines of tests against 3,300 lines of source, but the count matters less than the shape:
🧬 Mutation-verified guardrails — every safety control has a test proven to fail when the control is disabled. Written after discovering an
asyncio.Lockwhose removal kept the suite green.🌐 Zero network access — every upstream call is
respx-mocked. A test that needs the network is a broken test.🔍 A7 sweep — all 65 tools are called against an upstream returning secrets at four nesting depths, with a negative control asserting the fixture really contains them, so the sweep can't pass vacuously.
📐 Schema drift guard — operation counts, payload shapes, and the packaged data file are all asserted, so an upstream upgrade fails here rather than in production.
Design notes
Document | Contents |
Product contract — decisions D1–D13, controls S1–S8, acceptance criteria A1–A10 | |
All 68 operations with body fields and required-ness | |
Internal module interfaces | |
Two things the OpenAPI document gets wrong, measured against a live instance | |
Verbatim copy of the instance's |
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityCmaintenanceEnables management of Nginx Proxy Manager instances for configuring proxy hosts, requesting Let's Encrypt SSL certificates, and managing access lists. It allows users to control their web proxy infrastructure through natural language commands in MCP-compatible environments.503MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage Nginx Proxy Manager instances through natural language, covering 28 tools for proxy hosts, certificates, streams, and more.MIT
- AlicenseCqualityDmaintenanceMCP server that abstracts the Nginx Proxy Manager API, enabling management of proxy hosts, redirections, streams, certificates, access lists, and users through natural language.54171AGPL 3.0
- AlicenseAqualityAmaintenanceEnables natural language management of FastPanel 2 servers, including creating sites, databases, SSL certificates, and hardening nginx configurations.322MIT
Related MCP Connectors
Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
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/omichelbraga/nginx-proxy-manager-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server