mirror of
https://github.com/b3nw/nginx-proxy-manager-mcp.git
synced 2026-06-15 00:59:40 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c332c933d | |||
| 690408d6c7 |
@@ -134,8 +134,38 @@ Add to your `claude_desktop_config.json`:
|
|||||||
| `list_access_lists` | List access lists for authentication/IP restrictions |
|
| `list_access_lists` | List access lists for authentication/IP restrictions |
|
||||||
| `create_proxy_host` | Create a new proxy host |
|
| `create_proxy_host` | Create a new proxy host |
|
||||||
| `update_proxy_host` | Update an existing proxy host (v0.0.3+) |
|
| `update_proxy_host` | Update an existing proxy host (v0.0.3+) |
|
||||||
|
| `delete_proxy_host` | Delete a proxy host permanently (v0.0.4+) |
|
||||||
|
| `enable_proxy_host` | Enable (bring online) a disabled proxy host (v0.0.4+) |
|
||||||
|
| `disable_proxy_host` | Disable (take offline) a proxy host without deleting it (v0.0.4+) |
|
||||||
| `create_certificate` | Provision a new Let's Encrypt SSL certificate (v0.0.3+) |
|
| `create_certificate` | Provision a new Let's Encrypt SSL certificate (v0.0.3+) |
|
||||||
|
|
||||||
|
## Managing Proxy Host Lifecycle
|
||||||
|
|
||||||
|
Beyond creating and updating hosts, the server can delete a host outright or
|
||||||
|
toggle a host on and off without losing its configuration. Find the host ID
|
||||||
|
with `list_proxy_hosts` first.
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Take a host offline temporarily (config is preserved)
|
||||||
|
disable_proxy_host(42)
|
||||||
|
|
||||||
|
# Bring it back online
|
||||||
|
enable_proxy_host(42)
|
||||||
|
|
||||||
|
# Permanently remove a host (cannot be undone)
|
||||||
|
delete_proxy_host(42)
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `enable_proxy_host` / `disable_proxy_host` map to NPM's
|
||||||
|
`POST /nginx/proxy-hosts/{id}/enable` and `/disable` endpoints. If the host
|
||||||
|
is already in the requested state, NPM returns an HTTP 400 error
|
||||||
|
(e.g. `Host is already enabled`), which the tool surfaces as an API error.
|
||||||
|
- `delete_proxy_host` maps to `DELETE /nginx/proxy-hosts/{id}` and is
|
||||||
|
destructive — the reverse proxy stops serving the host's domains
|
||||||
|
immediately. Recreate it with `create_proxy_host` if you need it back.
|
||||||
|
|
||||||
## Log Access Setup
|
## Log Access Setup
|
||||||
|
|
||||||
The `get_proxy_host_logs` tool reads nginx log files directly from disk. Since NPM has no API for log retrieval, you need to mount NPM's log directory into the MCP container.
|
The `get_proxy_host_logs` tool reads nginx log files directly from disk. Since NPM has no API for log retrieval, you need to mount NPM's log directory into the MCP container.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "npm-mcp"
|
name = "npm-mcp"
|
||||||
version = "0.0.2"
|
version = "0.0.4"
|
||||||
description = "MCP server for Nginx Proxy Manager"
|
description = "MCP server for Nginx Proxy Manager"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -362,6 +362,71 @@ class NpmClient:
|
|||||||
response = await self._request("PUT", f"/nginx/proxy-hosts/{host_id}", json=payload)
|
response = await self._request("PUT", f"/nginx/proxy-hosts/{host_id}", json=payload)
|
||||||
return ProxyHost(**response.json())
|
return ProxyHost(**response.json())
|
||||||
|
|
||||||
|
async def delete_proxy_host(self, host_id: int) -> bool:
|
||||||
|
"""Delete a proxy host by ID.
|
||||||
|
|
||||||
|
Calls ``DELETE /nginx/proxy-hosts/{id}``. The reverse proxy stops
|
||||||
|
serving the host's domains immediately and the configuration is
|
||||||
|
removed. This is destructive and cannot be undone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The proxy host ID to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if NPM reported the host was deleted
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NpmNotFoundError: If the proxy host does not exist
|
||||||
|
NpmApiError: For other API errors
|
||||||
|
"""
|
||||||
|
response = await self._request("DELETE", f"/nginx/proxy-hosts/{host_id}")
|
||||||
|
# NPM returns the JSON literal `true` on a successful delete.
|
||||||
|
return bool(response.json())
|
||||||
|
|
||||||
|
async def enable_proxy_host(self, host_id: int) -> bool:
|
||||||
|
"""Enable (bring online) a proxy host by ID.
|
||||||
|
|
||||||
|
Calls ``POST /nginx/proxy-hosts/{id}/enable``. If the host is already
|
||||||
|
enabled, NPM responds with HTTP 400 ("Host is already enabled"), which
|
||||||
|
raises NpmApiError.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The proxy host ID to enable
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if NPM reported the host was enabled
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NpmNotFoundError: If the proxy host does not exist
|
||||||
|
NpmApiError: For other API errors
|
||||||
|
"""
|
||||||
|
response = await self._request("POST", f"/nginx/proxy-hosts/{host_id}/enable")
|
||||||
|
# NPM returns the JSON literal `true` on success.
|
||||||
|
return bool(response.json())
|
||||||
|
|
||||||
|
async def disable_proxy_host(self, host_id: int) -> bool:
|
||||||
|
"""Disable (take offline) a proxy host by ID.
|
||||||
|
|
||||||
|
Calls ``POST /nginx/proxy-hosts/{id}/disable``. The host's
|
||||||
|
configuration is preserved; the reverse proxy simply stops serving
|
||||||
|
its domains until it is re-enabled. If the host is already disabled,
|
||||||
|
NPM responds with HTTP 400 ("Host is already disabled"), which raises
|
||||||
|
NpmApiError.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The proxy host ID to disable
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if NPM reported the host was disabled
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NpmNotFoundError: If the proxy host does not exist
|
||||||
|
NpmApiError: For other API errors
|
||||||
|
"""
|
||||||
|
response = await self._request("POST", f"/nginx/proxy-hosts/{host_id}/disable")
|
||||||
|
# NPM returns the JSON literal `true` on success.
|
||||||
|
return bool(response.json())
|
||||||
|
|
||||||
async def create_certificate(
|
async def create_certificate(
|
||||||
self,
|
self,
|
||||||
domain_names: list[str],
|
domain_names: list[str],
|
||||||
|
|||||||
+117
-5
@@ -86,7 +86,7 @@ async def list_proxy_hosts() -> str:
|
|||||||
result = []
|
result = []
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
domains = ", ".join(host.domain_names)
|
domains = ", ".join(host.domain_names)
|
||||||
ssl_status = "🔒 SSL" if host.ssl_forced else "🔓 HTTP"
|
ssl_status = "\U0001f512 SSL" if host.ssl_forced else "\U0001f513 HTTP"
|
||||||
enabled_status = "✅" if host.enabled else "❌"
|
enabled_status = "✅" if host.enabled else "❌"
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
@@ -313,8 +313,8 @@ async def create_proxy_host(
|
|||||||
"""Create a new proxy host in Nginx Proxy Manager.
|
"""Create a new proxy host in Nginx Proxy Manager.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
domain_names: List of domain names (e.g., ["app.ext.ben.io"])
|
domain_names: List of domain names (e.g., ["app.example.com"])
|
||||||
forward_host: Backend host/IP to forward to (e.g., "192.168.1.100" or "container-name")
|
forward_host: Backend host/IP to forward to (e.g., "10.0.0.50" or "container-name")
|
||||||
forward_port: Backend port to forward to (e.g., 8080)
|
forward_port: Backend port to forward to (e.g., 8080)
|
||||||
forward_scheme: Backend protocol - "http" or "https" (default from config)
|
forward_scheme: Backend protocol - "http" or "https" (default from config)
|
||||||
certificate_id: SSL certificate ID. Use list_certificates to find available certs.
|
certificate_id: SSL certificate ID. Use list_certificates to find available certs.
|
||||||
@@ -335,10 +335,10 @@ async def create_proxy_host(
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
create_proxy_host(
|
create_proxy_host(
|
||||||
domain_names=["myapp.ext.ben.io"],
|
domain_names=["myapp.example.com"],
|
||||||
forward_host="10.0.0.50",
|
forward_host="10.0.0.50",
|
||||||
forward_port=3000,
|
forward_port=3000,
|
||||||
certificate_id=24, # *.ext.ben.io wildcard
|
certificate_id=24, # *.example.com wildcard
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -459,6 +459,118 @@ async def update_proxy_host(
|
|||||||
return _format_error(e)
|
return _format_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
async def delete_proxy_host(host_id: int) -> str:
|
||||||
|
"""Delete a proxy host from Nginx Proxy Manager.
|
||||||
|
|
||||||
|
Permanently removes the proxy host configuration via
|
||||||
|
DELETE /nginx/proxy-hosts/{id}. The reverse proxy stops serving the
|
||||||
|
host's domains immediately. This action cannot be undone — recreate the
|
||||||
|
host with create_proxy_host if you need it back.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The ID of the proxy host to delete (use list_proxy_hosts to find IDs)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation that the proxy host was deleted.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
delete_proxy_host(42) # permanently remove proxy host 42
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
client = get_client()
|
||||||
|
|
||||||
|
# Resolve the domains first so the confirmation message is meaningful.
|
||||||
|
domains: str | None = None
|
||||||
|
try:
|
||||||
|
host = await client.get_proxy_host(host_id)
|
||||||
|
domains = ", ".join(host.domain_names)
|
||||||
|
except Exception:
|
||||||
|
domains = None
|
||||||
|
|
||||||
|
await client.delete_proxy_host(host_id)
|
||||||
|
|
||||||
|
if domains:
|
||||||
|
return f"Successfully deleted proxy host [{host_id}] ({domains})."
|
||||||
|
return f"Successfully deleted proxy host [{host_id}]."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return _format_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
async def enable_proxy_host(host_id: int) -> str:
|
||||||
|
"""Enable a proxy host in Nginx Proxy Manager.
|
||||||
|
|
||||||
|
Brings a previously disabled proxy host back online via
|
||||||
|
POST /nginx/proxy-hosts/{id}/enable, so the reverse proxy serves its
|
||||||
|
domains again. If the host is already enabled, NPM returns an HTTP 400
|
||||||
|
error ("Host is already enabled"), which is surfaced as an API error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The ID of the proxy host to enable (use list_proxy_hosts to find IDs)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation that the proxy host was enabled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
enable_proxy_host(42) # bring proxy host 42 back online
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
client = get_client()
|
||||||
|
await client.enable_proxy_host(host_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
host = await client.get_proxy_host(host_id)
|
||||||
|
domains = ", ".join(host.domain_names)
|
||||||
|
return f"Successfully enabled proxy host [{host_id}] ({domains})."
|
||||||
|
except Exception:
|
||||||
|
return f"Successfully enabled proxy host [{host_id}]."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return _format_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
async def disable_proxy_host(host_id: int) -> str:
|
||||||
|
"""Disable a proxy host in Nginx Proxy Manager.
|
||||||
|
|
||||||
|
Takes a proxy host offline via POST /nginx/proxy-hosts/{id}/disable
|
||||||
|
without deleting it. The reverse proxy stops serving the host's domains
|
||||||
|
until it is re-enabled with enable_proxy_host; the configuration is
|
||||||
|
preserved. If the host is already disabled, NPM returns an HTTP 400
|
||||||
|
error ("Host is already disabled"), which is surfaced as an API error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
host_id: The ID of the proxy host to disable (use list_proxy_hosts to find IDs)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation that the proxy host was disabled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
disable_proxy_host(42) # take proxy host 42 offline, keep its config
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
client = get_client()
|
||||||
|
|
||||||
|
# Resolve domains before disabling (host stays readable while disabled).
|
||||||
|
domains: str | None = None
|
||||||
|
try:
|
||||||
|
host = await client.get_proxy_host(host_id)
|
||||||
|
domains = ", ".join(host.domain_names)
|
||||||
|
except Exception:
|
||||||
|
domains = None
|
||||||
|
|
||||||
|
await client.disable_proxy_host(host_id)
|
||||||
|
|
||||||
|
if domains:
|
||||||
|
return f"Successfully disabled proxy host [{host_id}] ({domains})."
|
||||||
|
return f"Successfully disabled proxy host [{host_id}]."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return _format_error(e)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def get_proxy_host_logs(
|
async def get_proxy_host_logs(
|
||||||
host_id: int,
|
host_id: int,
|
||||||
|
|||||||
Reference in New Issue
Block a user