Install
$ agentstack add mcp-jmagar-swag-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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). 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 templatesconfig/: local config and test assetsdocs/: template notes, test commands, and design records.claude-plugin/,.codex-plugin/,gemini-extension.json: client manifestsdocker-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.
/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
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
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 PROXYCONFSPATH when set. Accepts a local path or SSH URI. | | SWAGMCPSWAGLOGBASEPATH | no | /swag/log | Base path for SWAG log files (local or remote) | | SWAGMCPTEMPLATEPATH | 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 | | SWAGMCPNO_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/mcpand/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_hostsentries into the container (seeSWAG_MCP_SSH_HOST_PATHin.env.example) SWAG_MCP_SWAG_LOG_BASE_PATHshould 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 .backup.. 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
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 # pyt
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [jmagar](https://github.com/jmagar)
- **Source:** [jmagar/swag-mcp](https://github.com/jmagar/swag-mcp)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.