mirror of
https://github.com/b3nw/nginx-proxy-manager-mcp.git
synced 2026-05-20 23:45:48 -05:00
Compare commits
8 Commits
v0.0.3
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
| 77318c428e | |||
| 474784d4bc | |||
| 43d6ed4ca1 | |||
| a9011ff428 | |||
| ea137943fc | |||
| a9f4379c98 | |||
| e8df05e3f3 | |||
| 69158a871b |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
|
|||||||
@@ -131,8 +131,6 @@ Add to your `claude_desktop_config.json`:
|
|||||||
| `list_certificates` | List SSL certificates |
|
| `list_certificates` | List SSL certificates |
|
||||||
| `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+) |
|
|
||||||
| `create_certificate` | Provision a new Let's Encrypt SSL certificate (v0.0.3+) |
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "npm-mcp"
|
name = "npm-mcp"
|
||||||
version = "0.0.2"
|
version = "0.1.0"
|
||||||
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"
|
||||||
|
|||||||
@@ -323,72 +323,3 @@ class NpmClient:
|
|||||||
|
|
||||||
response = await self._request("POST", "/nginx/proxy-hosts", json=payload)
|
response = await self._request("POST", "/nginx/proxy-hosts", json=payload)
|
||||||
return ProxyHost(**response.json())
|
return ProxyHost(**response.json())
|
||||||
|
|
||||||
async def update_proxy_host(
|
|
||||||
self,
|
|
||||||
host_id: int,
|
|
||||||
**kwargs,
|
|
||||||
) -> ProxyHost:
|
|
||||||
"""Update an existing proxy host.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
host_id: The proxy host ID to update
|
|
||||||
**kwargs: Fields to update (same as create_proxy_host)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Updated ProxyHost object
|
|
||||||
"""
|
|
||||||
# Get existing host to merge with updates
|
|
||||||
existing = await self.get_proxy_host(host_id)
|
|
||||||
payload = {
|
|
||||||
"domain_names": existing.domain_names,
|
|
||||||
"forward_host": existing.forward_host,
|
|
||||||
"forward_port": existing.forward_port,
|
|
||||||
"forward_scheme": existing.forward_scheme,
|
|
||||||
"certificate_id": existing.certificate_id or 0,
|
|
||||||
"ssl_forced": existing.ssl_forced,
|
|
||||||
"hsts_enabled": existing.hsts_enabled,
|
|
||||||
"hsts_subdomains": existing.hsts_subdomains,
|
|
||||||
"http2_support": existing.http2_support,
|
|
||||||
"block_exploits": existing.block_exploits,
|
|
||||||
"caching_enabled": existing.caching_enabled,
|
|
||||||
"allow_websocket_upgrade": existing.allow_websocket_upgrade,
|
|
||||||
"access_list_id": existing.access_list_id,
|
|
||||||
"advanced_config": existing.advanced_config,
|
|
||||||
"meta": existing.meta,
|
|
||||||
}
|
|
||||||
payload.update({k: v for k, v in kwargs.items() if v is not None})
|
|
||||||
|
|
||||||
response = await self._request("PUT", f"/nginx/proxy-hosts/{host_id}", json=payload)
|
|
||||||
return ProxyHost(**response.json())
|
|
||||||
|
|
||||||
async def create_certificate(
|
|
||||||
self,
|
|
||||||
domain_names: list[str],
|
|
||||||
email: str,
|
|
||||||
provider: str = "letsencrypt",
|
|
||||||
dns_challenge: bool = False,
|
|
||||||
) -> Certificate:
|
|
||||||
"""Create/provision a new SSL certificate.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
domain_names: List of domain names for the certificate
|
|
||||||
email: Email address for Let's Encrypt notifications
|
|
||||||
provider: Certificate provider (default: "letsencrypt")
|
|
||||||
dns_challenge: Use DNS challenge instead of HTTP (default: False)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Created Certificate object
|
|
||||||
"""
|
|
||||||
payload = {
|
|
||||||
"domain_names": domain_names,
|
|
||||||
"meta": {
|
|
||||||
"letsencrypt_email": email,
|
|
||||||
"letsencrypt_agree": True,
|
|
||||||
"dns_challenge": dns_challenge,
|
|
||||||
},
|
|
||||||
"provider": provider,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await self._request("POST", "/nginx/certificates", json=payload)
|
|
||||||
return Certificate(**response.json())
|
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ class UserMeta(BaseModel):
|
|||||||
class Owner(BaseModel):
|
class Owner(BaseModel):
|
||||||
"""Proxy host owner information."""
|
"""Proxy host owner information."""
|
||||||
|
|
||||||
id: int | None = None
|
id: int
|
||||||
created_on: datetime | None = None
|
created_on: datetime
|
||||||
modified_on: datetime | None = None
|
modified_on: datetime
|
||||||
is_disabled: bool = False
|
is_disabled: bool
|
||||||
email: str | None = None
|
email: str
|
||||||
name: str = ""
|
name: str
|
||||||
nickname: str = ""
|
nickname: str
|
||||||
avatar: str = ""
|
avatar: str
|
||||||
roles: list[str] = Field(default_factory=list)
|
roles: list[str]
|
||||||
|
|
||||||
|
|
||||||
class AccessList(BaseModel):
|
class AccessList(BaseModel):
|
||||||
@@ -49,13 +49,13 @@ class AccessList(BaseModel):
|
|||||||
class Certificate(BaseModel):
|
class Certificate(BaseModel):
|
||||||
"""SSL Certificate information."""
|
"""SSL Certificate information."""
|
||||||
|
|
||||||
id: int | None = None
|
id: int
|
||||||
created_on: datetime | None = None
|
created_on: datetime
|
||||||
modified_on: datetime | None = None
|
modified_on: datetime
|
||||||
owner_user_id: int | None = None
|
owner_user_id: int
|
||||||
provider: str = ""
|
provider: str
|
||||||
nice_name: str = ""
|
nice_name: str
|
||||||
domain_names: list[str] = Field(default_factory=list)
|
domain_names: list[str]
|
||||||
expires_on: datetime | None = None
|
expires_on: datetime | None = None
|
||||||
meta: dict[str, Any] = Field(default_factory=dict)
|
meta: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|||||||
@@ -376,112 +376,3 @@ async def create_proxy_host(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return _format_error(e)
|
return _format_error(e)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def update_proxy_host(
|
|
||||||
host_id: int,
|
|
||||||
forward_host: str | None = None,
|
|
||||||
forward_port: int | None = None,
|
|
||||||
forward_scheme: str | None = None,
|
|
||||||
certificate_id: int | None = None,
|
|
||||||
ssl_forced: bool | None = None,
|
|
||||||
block_exploits: bool | None = None,
|
|
||||||
allow_websocket_upgrade: bool | None = None,
|
|
||||||
access_list_id: int | None = None,
|
|
||||||
advanced_config: str | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""Update an existing proxy host in Nginx Proxy Manager.
|
|
||||||
|
|
||||||
Only provided fields will be updated; all others remain unchanged.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
host_id: The ID of the proxy host to update
|
|
||||||
forward_host: Backend host/IP to forward to
|
|
||||||
forward_port: Backend port to forward to
|
|
||||||
forward_scheme: Backend protocol - "http" or "https"
|
|
||||||
certificate_id: SSL certificate ID (use list_certificates to find, 0 for none)
|
|
||||||
ssl_forced: Force HTTPS redirect
|
|
||||||
block_exploits: Enable common exploit blocking
|
|
||||||
allow_websocket_upgrade: Allow WebSocket connections
|
|
||||||
access_list_id: Access list ID (0 for no restrictions)
|
|
||||||
advanced_config: Custom nginx configuration block
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Details of the updated proxy host.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
client = get_client()
|
|
||||||
kwargs = {}
|
|
||||||
if forward_host is not None:
|
|
||||||
kwargs["forward_host"] = forward_host
|
|
||||||
if forward_port is not None:
|
|
||||||
kwargs["forward_port"] = forward_port
|
|
||||||
if forward_scheme is not None:
|
|
||||||
kwargs["forward_scheme"] = forward_scheme
|
|
||||||
if certificate_id is not None:
|
|
||||||
kwargs["certificate_id"] = certificate_id
|
|
||||||
if ssl_forced is not None:
|
|
||||||
kwargs["ssl_forced"] = ssl_forced
|
|
||||||
if block_exploits is not None:
|
|
||||||
kwargs["block_exploits"] = block_exploits
|
|
||||||
if allow_websocket_upgrade is not None:
|
|
||||||
kwargs["allow_websocket_upgrade"] = allow_websocket_upgrade
|
|
||||||
if access_list_id is not None:
|
|
||||||
kwargs["access_list_id"] = access_list_id
|
|
||||||
if advanced_config is not None:
|
|
||||||
kwargs["advanced_config"] = advanced_config
|
|
||||||
|
|
||||||
host = await client.update_proxy_host(host_id, **kwargs)
|
|
||||||
|
|
||||||
domains = ", ".join(host.domain_names)
|
|
||||||
return (
|
|
||||||
f"Successfully updated proxy host!\n\n"
|
|
||||||
f"ID: {host.id}\n"
|
|
||||||
f"Domains: {domains}\n"
|
|
||||||
f"Forward: {host.forward_scheme}://{host.forward_host}:{host.forward_port}\n"
|
|
||||||
f"SSL: {'Enabled' if host.ssl_forced else 'Disabled'}\n"
|
|
||||||
f"Certificate ID: {host.certificate_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return _format_error(e)
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
async def create_certificate(
|
|
||||||
domain_names: list[str],
|
|
||||||
email: str,
|
|
||||||
dns_challenge: bool = False,
|
|
||||||
) -> str:
|
|
||||||
"""Provision a new Let's Encrypt SSL certificate.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
domain_names: List of domain names for the certificate
|
|
||||||
email: Email address for Let's Encrypt notifications
|
|
||||||
dns_challenge: Use DNS challenge instead of HTTP (default: False)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Details of the created certificate including its ID.
|
|
||||||
Use the returned ID with create_proxy_host or update_proxy_host.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
client = get_client()
|
|
||||||
cert = await client.create_certificate(
|
|
||||||
domain_names=domain_names,
|
|
||||||
email=email,
|
|
||||||
dns_challenge=dns_challenge,
|
|
||||||
)
|
|
||||||
|
|
||||||
domains = ", ".join(cert.domain_names)
|
|
||||||
expiry = cert.expires_on.strftime("%Y-%m-%d") if cert.expires_on else "N/A"
|
|
||||||
return (
|
|
||||||
f"Successfully created certificate!\n\n"
|
|
||||||
f"ID: {cert.id}\n"
|
|
||||||
f"Provider: {cert.provider}\n"
|
|
||||||
f"Domains: {domains}\n"
|
|
||||||
f"Expires: {expiry}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return _format_error(e)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user