swag-mcp
SWAG reverse proxy configuration management via MCP tools. Create, edit, view, and manage nginx proxy configurations with auth integration.
Open source Open in the app JSON README (API)
About
SWAG reverse proxy configuration management via MCP tools. Create, edit, view, and manage nginx proxy configurations with auth integration.
Details
- Kind
- Plugins
- Topic
- Cloud & DevOps
- Publisher
- jmagar
- Origin
- gemini
- Category
- ferramentas
- Version
- 1.1.6
- Stars
- 5
- Open pull requests
- 1
- Last push
- 2026-09-07T09:25:28Z
- Repository state
- ativo
- Language
- Python
- License
- MIT
- Added
- 2026-08-30 14:13:39
- Updated
- 2026-08-30 14:13:39
- Origin id
jmagar/swag-mcp
README
# SWAG MCP
<!-- mcp-name: tv.tootie/swag-mcp -->
[](https://pypi.org/project/swag-mcp/) [](https://github.com/jmagar/swag-mcp/pkgs/container/swag-mcp)
MCP server for managing SWAG reverse-proxy configuration files, backups, logs, and health checks. Uses a single `swag` action router backed by local filesystem or SSH-accessible SWAG config storage.
## Overview
SWAG MCP generates and manages nginx subdomain proxy configurations for [SWAG (Secure Web Application Gateway)](https://github.com/linuxserver/docker-swag). Every generated config includes MCP-compatible security headers unconditionally, making configs suitable for both standard web services and MCP/AI services.
## What this repository ships
- `swag_mcp/`: server, config, middleware, models, services, tools, and templates
- `config/`: local config and test assets
- `docs/`: template notes, test commands, and design records
- `.claude-plugin/`, `.codex-plugin/`, `gemini-extension.json`: client manifests
- `docker-compose.yaml`, `Dockerfile`, `entrypoint.sh`: container deployment
## MCP surface
### Tool
| Tool | Purpose |
| --- | --- |
| `swag` | Unified action router for config, logs, backups, and health checks |
| `swag_help` | Return help for the SWAG MCP server — lists all available actions and sub-actions |
### Actions
| Action | Purpose | Required params |
| --- | --- | --- |
| `list` | List config files | none |
| `create` | Create a new reverse-proxy config | `config_name`, `server_name`, `upstream_app`, `upstream_port` |
| `view` | View config contents | `config_name` |
| `edit` | Replace config contents | `config_name`, `new_content` |
| `update` | Update a specific field | `config_name`, `update_field`, `update_value` |
| `remove` | Remove a config | `config_name` |
| `logs` | Read SWAG logs | none |
| `backups` | List or clean up backup files | `backup_action` |
| `health_check` | Probe a service endpoint | `domain` |
### Resources
| URI | Type | Description |
| --- | --- | --- |
| `swag://` | Directory | All active `.conf` files (excluding `.sample`) |
| `swag://configs/live` | Stream | Real-time config change events |
| `swag://health/stream` | Stream | Real-time health status for monitored services |
| `swag://logs/stream` | Stream | Live nginx error log tail |
## Installation
### Plugin (recommended)
Install as a Claude Code plugin. You will be prompted for:
- **SWAG Proxy Configs Path** -- local path to proxy-confs directory
- **SWAG MCP Server URL** -- base URL of the running HTTP server
- **SWAG MCP API Token** -- bearer token for the HTTP server
The plugin connects to the server's native streamable-HTTP endpoint.
```bash
/plugin marketplace add jmagar/claude-homelab
/plugin install swag-mcp @jmagar-claude-homelab
```
The plugin uses native HTTP transport and appends `/mcp` to the configured server URL.
### Docker Compose
```bash
cp .env.example .env
chmod 600 .env
# Edit .env with your paths and token
docker compose up -d
```
The container always runs on internal port 8000. Compose publishes it on
`127.0.0.1:49152` by default; set `SWAG_MCP_PORT` to control the host port and
`SWAG_MCP_BIND_ADDRESS` only when you intentionally expose it beyond loopback.
### Local development
```bash
just setup # copies .env.example and runs uv sync
just dev # starts the server
```
## Configuration
Two deployment paths are supported:
| Path | Transport | Credentials | Auth |
|------|-----------|-------------|------|
| **Plugin (HTTP)** | http | `userConfig` in plugin settings | Bearer token |
| **Docker (HTTP)** | http | `.env` file | Bearer token |
See [docs/CONFIG.md](docs/CONFIG.md) for full variable reference. All variables use the `SWAG_MCP_` prefix.
### Core
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_PROXY_CONFS_PATH` | no | `/swag/nginx/proxy-confs` | Local path to SWAG proxy confs directory |
| `SWAG_MCP_PROXY_CONFS_URI` | no | `` | Overrides `PROXY_CONFS_PATH` when set. Accepts a local path or SSH URI. |
| `SWAG_MCP_SWAG_LOG_BASE_PATH` | no | `/swag/log` | Base path for SWAG log files (local or remote) |
| `SWAG_MCP_TEMPLATE_PATH` | no | `templates` | Path to Jinja2 templates directory |
### Server
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_HOST` | no | `127.0.0.1` | Bind address for the MCP server |
| `SWAG_MCP_BIND_ADDRESS` | no | `127.0.0.1` | Docker host bind address for the published MCP port |
| `SWAG_MCP_PORT` | no | `49152` | Host-side port (Docker only; container always uses 8000) |
| `SWAG_MCP_TOKEN` | recommended | `` | Bearer token enforced by FastMCP for direct MCP server access |
| `SWAG_MCP_NO_AUTH` | no | `false` | Set `true` only for loopback/proxy-isolated deployments without server auth |
### Defaults
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_DEFAULT_WEB_AUTH_METHOD` | no | `authelia` | Web endpoint auth method used when `create` omits `auth_method`; this is not MCP server auth |
| `SWAG_MCP_DEFAULT_QUIC_ENABLED` | no | `false` | QUIC default for new configs |
### Backups
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_BACKUP_RETENTION_DAYS` | no | `30` | Days to keep backup files. Cleanup runs at startup and on `backups action=cleanup`. |
### Logging
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_LOG_LEVEL` | no | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` / `CRITICAL` |
| `SWAG_MCP_LOG_DIRECTORY` | no | `/app/.swag-mcp/logs` | Directory for rotating log files |
| `SWAG_MCP_LOG_FILE_ENABLED` | no | `true` | Write logs to files in addition to stdout |
| `SWAG_MCP_LOG_FILE_MAX_BYTES` | no | `10485760` | Max log file size before rotation (10 MB) |
| `SWAG_MCP_ENABLE_STRUCTURED_LOGGING` | no | `false` | Emit JSON structured logs |
| `SWAG_MCP_LOG_PAYLOADS` | no | `false` | Include request/response bodies in logs |
| `SWAG_MCP_LOG_PAYLOAD_MAX_LENGTH` | no | `1000` | Max payload length when `LOG_PAYLOADS=true` |
### Performance
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_SLOW_OPERATION_THRESHOLD_MS` | no | `1000` | Log a warning when any operation exceeds this threshold |
| `SWAG_MCP_ENABLE_RETRY_MIDDLEWARE` | no | `true` | Automatically retry failed operations |
| `SWAG_MCP_MAX_RETRIES` | no | `3` | Max retry attempts |
### Health checks
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_HEALTH_CHECK_INSECURE` | no | `false` | Skip SSL verification for health probes. Not recommended for production. |
### Rate limiting
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `SWAG_MCP_RATE_LIMIT_ENABLED` | no | `false` | Enable the rate-limiting middleware |
| `SWAG_MCP_RATE_LIMIT_RPS` | no | `10.0` | Sustained requests per second |
| `SWAG_MCP_RATE_LIMIT_BURST` | no | `20` | Burst capacity above the RPS limit |
## Authentication methods
Pass `auth_method` to `create` to control which SWAG auth snippet the config includes. The default is set by `SWAG_MCP_DEFAULT_WEB_AUTH_METHOD`.
| Method | When to use |
| --- | --- |
| `none` | Public services, or services behind an external SSO not managed by SWAG |
| `basic` | Simple HTTP basic auth. Credentials managed by SWAG. |
| `ldap` | LDAP/Active Directory authentication via SWAG's built-in LDAP support |
| `authelia` | Self-hosted SSO with 2FA. Most common for homelab services. |
| `authentik` | Self-hosted identity provider with rich policy engine |
| `tinyauth` | Lightweight single-user auth for internal tools |
| `oauth` | OAuth 2.1 gateway (`mcp-oauth`). Use for MCP/AI services that handle their own auth flow. |
`none` exposes the service without authentication. Only use it if access is restricted at the network or proxy layer.
## Template system
All configs are generated from a single Jinja2 template: `templates/mcp.subdomain.conf.j2`. Only subdomain-style configs are supported (`service.domain.tld`).
### Template variables
| Variable | Type | Required | Description |
| --- | --- | --- | --- |
| `service_name` | string | yes | Derived from `config_name` (e.g., `jellyfin`) |
| `server_name` | string | yes | Domain name (e.g., `media.example.com`) |
| `upstream_app` | string | yes | Container name or IP for the main service |
| `upstream_port` | integer | yes | Port the main service listens on |
| `upstream_proto` | string | yes | `http` or `https` |
| `mcp_upstream_app` | string | no | Container name or IP for the MCP endpoint. Defaults to `upstream_app`. |
| `mcp_upstream_port` | integer | no | Port for the MCP endpoint. Defaults to `upstream_port`. |
| `mcp_upstream_proto` | string | no | Protocol for the MCP endpoint. Defaults to `upstream_proto`. |
| `auth_method` | string | yes | One of the auth methods listed above |
| `enable_quic` | boolean | yes | Add `listen 443 quic` listeners and `Alt-Svc` header |
### MCP security headers
These headers are included unconditionally in every generated config, regardless of auth method:
| Header | Value | Purpose |
| --- | --- | --- |
| `X-MCP-Version` | `2025-11-25` | Advertises the MCP protocol version |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Limits referrer leakage |
DNS rebinding protection is also applied unconditionally. The `origin_valid` variable is set to `1` only for empty origins, `https://$server_name`, localhost variants, and `*.anthropic.com` / `*.claude.ai` origins. Requests to `/mcp` with an invalid origin receive a `403` JSON error.
## Split routing
Split routing sends the main application traffic to one upstream and MCP traffic to a different upstream. Use it when your MCP/AI service runs on a separate container or host (e.g., a GPU server).
Without split routing, both `/` and `/mcp` proxy to the same `upstream_app:upstream_port`.
With split routing:
- `/` and `/health` → `upstream_app:upstream_port`
- `/mcp` and `/session` → `mcp_upstream_app:mcp_upstream_port`
### Example: Jellyfin with a separate AI backend
```
action=create
config_name=jellyfin.subdomain.conf
server_name=jellyfin.example.com
upstream_app=jellyfin
upstream_port=8096
upstream_proto=http
mcp_upstream_app=ai-gpu-server
mcp_upstream_port=8080
mcp_upstream_proto=http
auth_method=authelia
```
This generates:
- `location /` → `http://jellyfin:8096` (protected by Authelia)
- `location /mcp` → `http://ai-gpu-server:8080` (protected by OAuth verify)
- `location /health` → `http://jellyfin:8096` (no auth, for health probes)
If `mcp_upstream_app` is omitted, all three locations proxy to `jellyfin:8096`.
## SSH / remote configuration
Set `SWAG_MCP_PROXY_CONFS_URI` to manage configs on a remote SWAG host over SSH.
Supported URI formats:
```
# Default key, default port 22
SWAG_MCP_PROXY_CONFS_URI=swag-server:/mnt/appdata/swag/nginx/proxy-confs
# Explicit user
SWAG_MCP_PROXY_CONFS_URI=admin@swag-server:/mnt/appdata/swag/nginx/proxy-confs
# Explicit user and port
SWAG_MCP_PROXY_CONFS_URI=admin@swag-server:2222:/mnt/appdata/swag/nginx/proxy-confs
```
Requirements:
- SSH key-based (passwordless) access to the remote host
- Mount an app-specific SSH directory with only the needed key, config, and `known_hosts` entries into the container (see `SWAG_MCP_SSH_HOST_PATH` in `.env.example`)
- `SWAG_MCP_SWAG_LOG_BASE_PATH` should point to the log base path on the same remote host
When `SWAG_MCP_PROXY_CONFS_URI` is set it takes precedence over `SWAG_MCP_PROXY_CONFS_PATH`.
## Usage examples
### List
```
swag action=list
swag action=list list_filter=active
swag action=list list_filter=samples query=plex offset=0 limit=20
```
`list_filter` accepts `all` (default), `active` (`.conf` files only), or `samples` (`.conf.sample` files only).
### Create
```
swag action=create
config_name=jellyfin.subdomain.conf
server_name=jellyfin.example.com
upstream_app=jellyfin
upstream_port=8096
```
After creation, a health check runs automatically against `server_name`.
### View
```
swag action=view config_name=jellyfin.subdomain.conf
```
### Edit
Replaces the full file content:
```
swag action=edit
config_name=jellyfin.subdomain.conf
new_content="..."
create_backup=true
```
### Update
Updates a single field in place:
```
swag action=update config_name=jellyfin.subdomain.conf update_field=port update_value=8097
swag action=update config_name=jellyfin.subdomain.conf update_field=upstream update_value=jellyfin-new
swag action=update config_name=jellyfin.subdomain.conf update_field=app update_value=jellyfin:8097
swag action=update config_name=jellyfin.subdomain.conf update_field=add_mcp update_value=/mcp
```
`update_field` options:
| Value | `update_value` format | Effect |
| --- | --- | --- |
| `port` | port number | Updates `upstream_port` |
| `upstream` | container name | Updates `upstream_app` |
| `app` | `app:port` | Updates both `upstream_app` and `upstream_port` |
| `add_mcp` | path (e.g., `/mcp`) | Injects an MCP location block into the config |
After an update, a health check runs automatically.
### Remove
```
swag action=remove config_name=jellyfin.subdomain.conf
swag action=remove config_name=jellyfin.subdomain.conf create_backup=false
```
### Logs
```
swag action=logs log_type=nginx-error lines=100
swag action=logs log_type=nginx-access lines=50
swag action=logs log_type=fail2ban lines=200
```
#### Log types and format
| `log_type` | Source file | Format |
| --- | --- | --- |
| `nginx-access` | `$SWAG_LOG_BASE_PATH/nginx/access.log` | Combined nginx access log: IP, timestamp, method, path, status, bytes, referrer, user-agent |
| `nginx-error` | `$SWAG_LOG_BASE_PATH/nginx/error.log` | Nginx error format: timestamp, level, PID, message |
| `fail2ban` | `$SWAG_LOG_BASE_PATH/fail2ban/fail2ban.log` | fail2ban log: timestamp, level, jail name, action, IP |
| `letsencrypt` | `$SWAG_LOG_BASE_PATH/letsencrypt/letsencrypt.log` | Certbot output: certificate request, renewal, errors |
| `renewal` | `$SWAG_LOG_BASE_PATH/letsencrypt/renewal.log` | Periodic renewal cron output |
`lines` accepts 1–1000 (default 50).
### Backups
```
swag action=backups backup_action=list
swag action=backups backup_action=cleanup retention_days=7
swag action=backups backup_action=cleanup retention_days=0
```
`backup_action=cleanup` removes backup files older than `retention_days`. Pass `retention_days=0` to use the server default (`SWAG_MCP_BACKUP_RETENTION_DAYS`).
Backup files are named `<original>.backup.<timestamp>`. They are created automatically before any `edit`, `update`, or `remove` operation when `create_backup=true` (the default).
Cleanup also runs at server startup. The startup cleanup uses `SWAG_MCP_BACKUP_RETENTION_DAYS`.
### Health check
```
swag action=health_check domain=jellyfin.example.com
swag action=health_check domain=jellyfin.example.com timeout=10 follow_redirects=false
```
#### Response fields
| Field | Type | Description |
| --- | --- | --- |
| `success` | boolean | `true` only for 2xx responses, plus 406 on `/mcp` because that confirms the MCP endpoint exists but requires POST. Other statuses fail and are reported in `endpoint_results`. |
| `domain` | string | Domain that was checked |
| `status_code` | integer or null | HTTP status code returned |
| `response_time_ms` | integer or null | Round-trip time in milliseconds |
| `error` | string or null | Error message if the probe failed |
| `endpoint_results` | array | Per-endpoint probe attempts with URL, success flag, status, timing, and error detail |
`timeout` accepts 1–300 seconds (default 30). The server adds a 10-second buffer on top of `timeout` for its own wait.
SSL certificate verification can be disabled globally with `SWAG_MCP_HEALTH_CHECK_INSECURE=true`.
## Development commands
```bash
just setup # copy .env.example and install dependencies
just dev # start the server with uv
just lint # ruff check
just fmt # ruff format
just typecheck # ty check
just test # pytest
just build # build a local swag-mcp image for manual testing
just up # docker compose up -d
just down # docker compose down
just logs # docker compose logs -f
just health # curl http://127.0.0.1:${SWAG_MCP_PORT:-49152}/health | jq
just gen-token # generate a random bearer token
```
## Operations
The Docker service publishes `127.0.0.1:49152` by default. Keep production MCP
ports on high-numbered, documented ports and run `just preflight` before
deployment to validate the external Docker network and selected host port.
Operational failure signals include Docker health becoming unhealthy, repeated
tool error responses, timeout messages for create/edit/update/log operations,
slow-operation warnings, health-check failures for known-good domains, and
nginx error log spikes. Use [docs/mcp/LOGS.md](docs/mcp/LOGS.md) for signal
triage and [docs/mcp/DEPLOY.md](docs/mcp/DEPLOY.md) for rollback and restore.
Backups are created before destructive edits, updates, and removals. To restore,
list backups with `swag(action="backups", backup_action="list")`, copy the
chosen backup content back with `swag(action="edit", create_backup=true)`,
validate nginx syntax, reload SWAG, and record the restore in
`docs/deployment-log.md`.
## Verification
```bash
just lint
just typecheck
just test
```
Check the server health endpoint:
```bash
just health
# {"status": "healthy", "service": "swag-mcp", "version": "1.1.6"}
```
The `/health` endpoint is also used by the Docker `HEALTHCHECK` directive. It always returns:
| Field | Value |
| --- | --- |
| `status` | `"healthy"` |
| `service` | `"swag-mcp"` |
| `version` | Package version from `importlib.metadata` |
## Related plugins
| Plugin | Category | Description |
|--------|----------|-------------|
| [homelab-core](https://github.com/jmagar/claude-homelab) | core | Core agents, commands, skills, and setup/health workflows for homelab management. |
| [overseerr-mcp](https://github.com/jmagar/overseerr-mcp) | media | Search movies and TV shows, submit requests, and monitor failed requests via Overseerr. |
| [unraid-mcp](https://github.com/jmagar/unraid-mcp) | infrastructure | Query, monitor, and manage Unraid servers: Docker, VMs, array, parity, and live telemetry. |
| [unifi-mcp](https://github.com/jmagar/unifi-mcp) | infrastructure | Monitor and manage UniFi devices, clients, firewall rules, and network health. |
| [gotify-mcp](https://github.com/jmagar/gotify-mcp) | utilities | Send and manage push notifications via a self-hosted Gotify server. |
| [synapse-mcp](https://github.com/jmagar/synapse-mcp) | infrastructure | Docker management (Flux) and SSH remote operations (Scout) across homelab hosts. |
| [arcane-mcp](https://github.com/jmagar/arcane-mcp) | infrastructure | Manage Docker environments, containers, images, volumes, networks, and GitOps via Arcane. |
| [syslog-mcp](https://github.com/jmagar/syslog-mcp) | infrastructure | Receive, index, and search syslog streams from all homelab hosts via SQLite FTS5. |
| [plugin-lab](https://github.com/jmagar/plugin-lab) | dev-tools | Scaffold, review, align, and deploy homelab MCP plugins with agents and canonical templates. |
## License
MIT